You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by pa...@apache.org on 2025/12/02 08:22:57 UTC

(hbase) branch branch-3 updated: HBASE-29531 Migrate RegionServer Status Jamon page back to JSP (#7491)

This is an automated email from the ASF dual-hosted git repository.

paksyd pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-3 by this push:
     new 7dd4e55c876 HBASE-29531 Migrate RegionServer Status Jamon page back to JSP (#7491)
7dd4e55c876 is described below

commit 7dd4e55c876dadc365d908d6778b1c64cf63689b
Author: Dávid Paksy <pa...@apache.org>
AuthorDate: Tue Dec 2 09:22:50 2025 +0100

    HBASE-29531 Migrate RegionServer Status Jamon page back to JSP (#7491)
    
    * HBASE-29531 Migrate RegionServer Status Jamon page back to JSP (#7371)
    
    This is the 2/3 step of the Jamon to JSP migration: the Region Server Status page.
    
    Did the migration the same way as for the Master Status page: https://github.com/apache/hbase/pull/6875
    
    Migrated the Jamon code to JSP as close as possible. Extracted the duplicated `formatZKString` method to new java class: ZKStringFormatter and added unit tests.
    
    Changed the Region Server Status page back to `/regionserver.jsp`. Made sure that `/rs-status` redirects to `/regionserver.jsp`.
    
    Extracted the BlockCache inline CSS styles to `hbase.css` file. Also extracted the large BlockCache Hit Ratio periods paging JavaScript code to separate .js file.
    
    Introduced a `src/main/resources/hbase-webapps/common` directory where we can place common JSP files which are used by both Master and RegionServer JSP pages. This required to adjust the JSP compiler Maven Antrun plugin a bit.
    
    Extracted the inline tablesorter initialization JavaScript code to separate file.
    
    Signed-off-by: Duo Zhang <zh...@apache.org>
    (cherry picked from commit 78922077a69765f91071b35fe77c323e253243cb)
    
    * [ADDENDUM] HBASE-29223 Fix TestMasterStatusUtil (#7416)
    
    TestMasterStatusUtil.testGetFragmentationInfoTurnedOn failed in master nightly build
    
    Signed-off-by: Nihal Jain <ni...@apache.org>
    Signed-off-by: Duo Zhang <zh...@apache.org>
    (cherry picked from commit 8ef271f837397688c26e215f2eee6e80408eb799)
---
 hbase-server/pom.xml                               |  18 +-
 .../hadoop/hbase/tmpl/common/TaskMonitorTmpl.jamon | 140 -----
 .../hbase/tmpl/regionserver/BlockCacheTmpl.jamon   | 564 ---------------------
 .../tmpl/regionserver/BlockCacheViewTmpl.jamon     |  80 ---
 .../hbase/tmpl/regionserver/RSStatusTmpl.jamon     | 330 ------------
 .../hbase/tmpl/regionserver/RegionListTmpl.jamon   | 338 ------------
 .../tmpl/regionserver/ReplicationStatusTmpl.jamon  | 105 ----
 .../tmpl/regionserver/ServerMetricsTmpl.jamon      | 265 ----------
 .../hadoop/hbase/master/http/MasterStatusUtil.java |   2 +-
 .../hbase/regionserver/http/RSStatusConstants.java |  41 ++
 .../hbase/regionserver/http/RSStatusServlet.java   |  33 +-
 .../hadoop/hbase/util/ZKStringFormatter.java       |  57 +++
 .../{master => common}/taskMonitor.jsp             |   0
 .../{master => common}/taskMonitorRenderTasks.jsp  |   0
 .../main/resources/hbase-webapps/master/hbck.jsp   |   2 +-
 .../main/resources/hbase-webapps/master/master.jsp |   2 +
 .../resources/hbase-webapps/master/rsgroup.jsp     |  31 +-
 .../hbase-webapps/master/softwareAttributes.jsp    |  34 +-
 .../main/resources/hbase-webapps/master/table.jsp  |  10 +-
 .../hbase-webapps/regionserver/blockCache.jsp      |  72 +++
 .../{regionserver.jsp => blockCacheBaseInfo.jsp}   |  24 +-
 .../regionserver/blockCacheConfig.jsp              |  72 +++
 .../regionserver/blockCacheEvictions.jsp           |  45 ++
 .../hbase-webapps/regionserver/blockCacheHits.jsp  |  92 ++++
 .../hbase-webapps/regionserver/blockCacheLevel.jsp | 159 ++++++
 .../hbase-webapps/regionserver/blockCacheStats.jsp |  58 +++
 .../hbase-webapps/regionserver/blockCacheView.jsp  |  62 +++
 .../hbase-webapps/regionserver/footer.jsp          |  10 +-
 .../hbase-webapps/regionserver/header.jsp          |   4 +-
 .../hbase-webapps/regionserver/index.html          |   2 +-
 .../hbase-webapps/regionserver/regionList.jsp      |  75 +++
 .../regionserver/regionListBaseInfo.jsp            |  58 +++
 .../regionserver/regionListCompactStats.jsp        |  79 +++
 .../regionserver/regionListMemstoreStats.jsp       |  70 +++
 .../regionserver/regionListRequestStats.jsp        |  61 +++
 .../regionserver/regionListStoreStats.jsp          | 119 +++++
 .../hbase-webapps/regionserver/regionserver.jsp    | 124 ++++-
 .../regionserver/replicationStatus.jsp             |  53 ++
 .../regionserver/replicationStatusCurrentLog.jsp   |  47 ++
 .../replicationStatusReplicationDelay.jsp          |  44 ++
 .../regionserver/{footer.jsp => scripts.jsp}       |   2 -
 .../hbase-webapps/regionserver/serverMetrics.jsp   |  55 ++
 .../regionserver/serverMetricsBaseStats.jsp        |  49 ++
 .../serverMetricsByteBuffAllocatorStats.jsp        |  51 ++
 .../regionserver/serverMetricsMemoryStats.jsp      |  78 +++
 .../regionserver/serverMetricsQueueStats.jsp       |  53 ++
 .../regionserver/serverMetricsRequestStats.jsp     |  46 ++
 .../regionserver/serverMetricsStoreStats.jsp       |  49 ++
 .../regionserver/serverMetricsWalStats.jsp         |  50 ++
 .../regionserver/softwareAttributes.jsp            | 114 +++++
 .../resources/hbase-webapps/static/css/hbase.css   |  29 ++
 .../hbase-webapps/static/js/blockCacheInit.js      | 120 +++++
 .../static/js/regionServerStatusInit.js            |  86 ++++
 .../org/apache/hadoop/hbase/TestInfoServers.java   |   5 +-
 .../hbase/master/http/TestMasterStatusUtil.java    |   6 +-
 .../hbase/regionserver/http/TestRSStatusPage.java  | 178 +++++++
 .../regionserver/http/TestRSStatusServlet.java     | 141 ------
 .../hadoop/hbase/util/TestZKStringFormatter.java   |  74 +++
 hbase-shell/src/main/ruby/hbase/taskmonitor.rb     |   6 +-
 59 files changed, 2502 insertions(+), 2072 deletions(-)

diff --git a/hbase-server/pom.xml b/hbase-server/pom.xml
index 2ddcf0415e6..e27ab227887 100644
--- a/hbase-server/pom.xml
+++ b/hbase-server/pom.xml
@@ -564,12 +564,26 @@
                     <exclude name="**/*~"/>
                   </fileset>
                 </copy>
+                <!-- Create a "staging" JSP directory where we copy the common JSP files into master and rs directories.
+                     Compilation is done using this "staging" JSP directory. -->
+                <mkdir dir="${project.build.directory}/jsp"/>
+                <copy todir="${project.build.directory}/jsp">
+                  <fileset dir="${src.webapps}">
+                    <include name="**/*.jsp"/>
+                  </fileset>
+                </copy>
+                <copy todir="${project.build.directory}/jsp/master">
+                  <fileset dir="${project.build.directory}/jsp/common"/>
+                </copy>
+                <copy todir="${project.build.directory}/jsp/regionserver">
+                  <fileset dir="${project.build.directory}/jsp/common"/>
+                </copy>
                 <!--The compile.classpath is passed in by maven -->
                 <taskdef classname="org.apache.jasper.JspC" classpathref="maven.compile.classpath" name="jspcompiler"/>
                 <mkdir dir="${build.webapps}/master/WEB-INF"/>
-                <jspcompiler outputdir="${generated.sources}/java" package="org.apache.hadoop.hbase.generated.master" uriroot="${src.webapps}/master" webxml="${build.webapps}/master/WEB-INF/web.xml"/>
+                <jspcompiler outputdir="${generated.sources}/java" package="org.apache.hadoop.hbase.generated.master" uriroot="${project.build.directory}/jsp/master" webxml="${build.webapps}/master/WEB-INF/web.xml"/>
                 <mkdir dir="${build.webapps}/regionserver/WEB-INF"/>
-                <jspcompiler outputdir="${generated.sources}/java" package="org.apache.hadoop.hbase.generated.regionserver" uriroot="${src.webapps}/regionserver" webxml="${build.webapps}/regionserver/WEB-INF/web.xml"/>
+                <jspcompiler outputdir="${generated.sources}/java" package="org.apache.hadoop.hbase.generated.regionserver" uriroot="${project.build.directory}/jsp/regionserver" webxml="${build.webapps}/regionserver/WEB-INF/web.xml"/>
               </target>
             </configuration>
           </execution>
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/common/TaskMonitorTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/common/TaskMonitorTmpl.jamon
deleted file mode 100644
index 8798e930c70..00000000000
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/common/TaskMonitorTmpl.jamon
+++ /dev/null
@@ -1,140 +0,0 @@
-<%doc>
-
-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.
-</%doc>
-<%import>
-java.util.*;
-org.apache.hadoop.hbase.monitoring.*;
-org.apache.hadoop.util.StringUtils;
-</%import>
-<%args>
-TaskMonitor taskMonitor = TaskMonitor.get();
-String filter = "general";
-String format = "html";
-String parent = "";
-</%args>
-
-<%if format.equals("json")%>
-    <& renderTasks; filter=filter &>
-<%else>
-  <h2><a name="tasks">Tasks</a></h2>
-
-  <div class="tabbable">
-      <ul class="nav nav-pills" role="tablist">
-        <li class="nav-item">
-            <a class="nav-link" href="#tab_alltasks" data-bs-toggle="tab" role="tab">Show All Monitored Tasks</a>
-        </li>
-        <li class="nav-item">
-            <a class="nav-link active" href="#tab_generaltasks" data-bs-toggle="tab" role="tab">Show non-RPC Tasks</a>
-        </li>
-        <li class="nav-item">
-            <a class="nav-link" href="#tab_handlertasks" data-bs-toggle="tab" role="tab">Show All RPC Handler Tasks</a>
-        </li>
-        <li class="nav-item">
-            <a class="nav-link" href="#tab_rpctasks" data-bs-toggle="tab" role="tab">Show Active RPC Calls</a>
-        </li>
-        <li class="nav-item">
-            <a class="nav-link" href="#tab_operationtasks" data-bs-toggle="tab" role="tab">Show Client Operations</a>
-        </li>
-      </ul>
-      <div class="tab-content">
-        <div class="tab-pane" id="tab_alltasks" role="tabpanel">
-            <& jsonView; filter="all" &>
-            <& renderTasks; filter="all" &>
-        </div>
-        <div class="tab-pane active" id="tab_generaltasks" role="tabpanel">
-            <& jsonView; filter="general" &>
-            <& renderTasks; filter="general" &>
-        </div>
-        <div class="tab-pane" id="tab_handlertasks" role="tabpanel">
-            <& jsonView; filter="handler" &>
-            <& renderTasks; filter="handler" &>
-        </div>
-        <div class="tab-pane" id="tab_rpctasks" role="tabpanel">
-            <& jsonView; filter="rpc" &>
-            <& renderTasks; filter="rpc" &>
-        </div>
-        <div class="tab-pane" id="tab_operationtasks" role="tabpanel">
-            <& jsonView; filter="operation" &>
-            <& renderTasks; filter="operation" &>
-        </div>
-      </div>
-  </div>
-</%if>
-
-<%def jsonView>
-    <%args>
-        String filter;
-    </%args>
-    <a href="<% parent %>?format=json&filter=<% filter %>">View as JSON</a>
-</%def>
-
-<%def renderTasks>
-    <%args>
-        String filter;
-    </%args>
-    <%java>
-       List<? extends MonitoredTask> tasks = taskMonitor.getTasks(filter);
-       long now = System.currentTimeMillis();
-       Collections.sort(tasks, (t1, t2) -> Long.compare(t1.getStateTime(), t2.getStateTime()));
-       boolean first = true;
-    </%java>
-    <%if format.equals("json") %>
-        [<%for MonitoredTask task : tasks%><%if first%><%java>first = false;</%java><%else>,</%if><% task.toJSON() %></%for>]
-    <%else>
-        <%if tasks.isEmpty()%>
-            <p>No tasks currently running on this node.</p>
-        <%else>
-            <table class="table table-striped">
-                <tr>
-                      <th>Start Time</th>
-                      <th>Description</th>
-                      <th>State</th>
-                      <th>Status</th>
-                      <th>Completion Time</th>
-                </tr>
-                <%for MonitoredTask task : tasks %>
-                    <tr class="<& stateCss; state = task.getState() &>">
-                      <td><% new Date(task.getStartTime()) %></td>
-                      <td><% task.getDescription() %></td>
-                      <td><% task.getState() %>
-                          (since <% StringUtils.formatTimeDiff(now, task.getStateTime()) %> ago)
-                      </td>
-                      <td><% task.getStatus() %>
-                          (since <% StringUtils.formatTimeDiff(now, task.getStatusTime()) %>
-                          ago)</td>
-                      <td>
-                        <%if task.getCompletionTimestamp() < 0 %>
-                          <% task.getState() %>
-                        <%else>
-                          <% new Date(task.getCompletionTimestamp()) %> (since <% StringUtils.formatTimeDiff(now, task.getCompletionTimestamp()) %> ago)
-                        </%if>
-                      </td>
-                    </tr>
-                </%for>
-            </table>
-        </%if>
-    </%if>
-</%def>
-
-<%def stateCss>
-        <%args>
-        MonitoredTask.State state;
-        </%args>
-<%java>       if (state == MonitoredTask.State.COMPLETE) { </%java>alert alert-success<%java> } </%java>
-<%java>  else if (state == MonitoredTask.State.ABORTED)  { </%java>alert alert-danger<%java>   } </%java>
-</%def>
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/BlockCacheTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/BlockCacheTmpl.jamon
deleted file mode 100644
index 82609aad719..00000000000
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/BlockCacheTmpl.jamon
+++ /dev/null
@@ -1,564 +0,0 @@
-<%doc>
-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.
-
-Template for rendering Block Cache tabs in RegionServer Status page.
-</%doc>
-<%args>
-CacheConfig cacheConfig;
-Configuration config;
-BlockCache bc;
-</%args>
-<%java>
-  String bcUrl = bc == null ? null : "http://hbase.apache.org/devapidocs/" + bc.getClass().getName().replaceAll("\\.", "/") + ".html";
-  String bcName = bc == null ? null : bc.getClass().getSimpleName();
-  BlockCache [] bcs = bc == null ? null : bc.getBlockCaches();
-  boolean evictions = bcs != null && bcs.length > 1;
-</%java>
-<%import>
-java.util.Map;
-org.apache.hadoop.hbase.io.hfile.BlockCacheUtil;
-org.apache.hadoop.hbase.io.hfile.BlockCacheUtil.CachedBlocksByFile;
-org.apache.hadoop.hbase.io.hfile.AgeSnapshot;
-org.apache.hadoop.hbase.io.hfile.CachedBlock;
-org.apache.hadoop.conf.Configuration;
-org.apache.hadoop.hbase.io.hfile.CacheConfig;
-org.apache.hadoop.hbase.io.hfile.BlockCache;
-org.apache.hadoop.hbase.io.hfile.LruBlockCache;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketCacheStats;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketAllocator;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketAllocator.Bucket;
-org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
-</%import>
-<div class="tabbable">
-    <ul class="nav nav-pills" role="tablist">
-        <li class="nav-item"><a class="nav-link active" href="#tab_bc_baseInfo" data-bs-toggle="tab" role="tab">Base Info</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_bc_config" data-bs-toggle="tab" role="tab">Config</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_bc_stats" data-bs-toggle="tab" role="tab">Stats</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_bc_l1" data-bs-toggle="tab" role="tab">L1</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_bc_l2" data-bs-toggle="tab" role="tab">L2</a></li>
-    </ul>
-    <div class="tab-content">
-        <div class="tab-pane active" id="tab_bc_baseInfo" role="tabpanel">
-            <& bc_baseInfo; bc = bc; bcUrl = bcUrl; bcName = bcName;  &>
-        </div>
-        <div class="tab-pane" id="tab_bc_config" role="tabpanel">
-            <& bc_config; cacheConfig = cacheConfig &>
-        </div>
-        <div class="tab-pane" id="tab_bc_stats" role="tabpanel">
-            <& bc_stats; bc = bc &>
-        </div>
-        <div class="tab-pane" id="tab_bc_l1" role="tabpanel">
-            <& bc_l; bc = bcs == null? bc: bcs[0]; name = "L1"; evictions = evictions; &>
-        </div>
-        <div class="tab-pane" id="tab_bc_l2" role="tabpanel">
-            <& bc_l; bc = bcs == null? null: bcs.length <= 1? null: bcs[1]; name = "L2"; evictions = evictions; &>
-        </div>
-    </div>
-</div>
-
-<%def bc_baseInfo>
-<%args>
-    BlockCache bc;
-    String bcUrl;
-    String bcName;
-</%args>
-<%java>
-  BlockCache [] bcs = bc == null? null: bc.getBlockCaches();
-  String bcl1Url = null;
-  String bcl1Name = null;
-  String bcl2Url = null;
-  String bcl2Name = null;
-  if (bcs != null) {
-    BlockCache bcl1 = bcs[0];
-    if (bcl1 != null) {
-      bcl1Url = "http://hbase.apache.org/devapidocs/" + bcl1.getClass().getName().replaceAll("\\.", "/") + ".html";
-      bcl1Name = bcl1.getClass().getSimpleName();
-    }
-    if (bcs.length == 2) {
-      BlockCache bcl2 = bcs[1];
-      bcl2Url = "http://hbase.apache.org/devapidocs/" + bcl2.getClass().getName().replaceAll("\\.", "/") + ".html";
-      bcl2Name = bcl2.getClass().getSimpleName();
-    }
-  }
-</%java>
-<table class="table table-striped">
-    <tr>
-        <th>Attribute</th>
-        <th>Value</th>
-        <th>Description</th>
-    </tr>
-    </tr>
-    <tr>
-        <td>Implementation</td>
-        <td><a href="<% bcUrl %>"><% bcName %></a></td>
-        <td>Block cache implementing class</td>
-    </tr>
-</table>
-<p>See <a href="http://hbase.apache.org/book.html#block.cache">block cache</a> in the HBase Reference Guide for help.</p>
-</%def>
-
-<%def bc_config>
-<%args>
-    CacheConfig cacheConfig;
-</%args>
-<%if cacheConfig == null %>
-<p>CacheConfig is null</p>
-<%else>
-<table class="table table-striped">
-    <tr>
-        <th>Attribute</th>
-        <th>Value</th>
-        <th>Description</th>
-    </tr>
-    <tr>
-        <td>Cache DATA on Read</td>
-        <td><% cacheConfig.shouldCacheDataOnRead() %></td>
-        <td>True if DATA blocks are cached on read
-        (INDEX & BLOOM blocks are always cached)</td>
-    </tr>
-    <tr>
-        <td>Cache DATA on Write</td>
-        <td><% cacheConfig.shouldCacheDataOnWrite() %></td>
-        <td>True if DATA blocks are cached on write.</td>
-    </tr>
-    <tr>
-        <td>Cache INDEX on Write</td>
-        <td><% cacheConfig.shouldCacheIndexesOnWrite() %></td>
-        <td>True if INDEX blocks are cached on write</td>
-    </tr>
-    <tr>
-        <td>Cache BLOOM on Write</td>
-        <td><% cacheConfig.shouldCacheBloomsOnWrite() %></td>
-        <td>True if BLOOM blocks are cached on write</td>
-    </tr>
-    <tr>
-        <td>Evict blocks on Close</td>
-        <td><% cacheConfig.shouldEvictOnClose() %></td>
-        <td>True if blocks are evicted from cache when an HFile
-        reader is closed</td>
-    </tr>
-    <tr>
-        <td>Cache DATA in compressed format</td>
-        <td><% cacheConfig.shouldCacheDataCompressed() %></td>
-        <td>True if DATA blocks are cached in their compressed form</td>
-    </tr>
-    <tr>
-        <td>Prefetch on Open</td>
-        <td><% cacheConfig.shouldPrefetchOnOpen() %></td>
-        <td>True if blocks are prefetched into cache on open</td>
-    </tr>
-</table>
-</%if>
-</%def>
-
-<%def evictions_tmpl>
-<%args>
-    BlockCache bc;
-</%args>
-<%java>
-  AgeSnapshot ageAtEvictionSnapshot = bc.getStats().getAgeAtEvictionSnapshot();
-  // Only show if non-zero mean and stddev as is the case in combinedblockcache
-</%java>
-    <tr>
-        <td>Evicted</td>
-        <td><% String.format("%,d", bc.getStats().getEvictedCount()) %></td>
-        <td>The total number of blocks evicted</td>
-    </tr>
-    <tr>
-        <td>Evictions</td>
-        <td><% String.format("%,d", bc.getStats().getEvictionCount()) %></td>
-        <td>The total number of times an eviction has occurred</td>
-    </tr>
-    <tr>
-        <td>Mean</td>
-        <td><% String.format("%,d", (long)ageAtEvictionSnapshot.getMean()) %></td>
-        <td>Mean age of Blocks at eviction time (seconds)</td>
-    </tr>
-</%def>
-
-<%def hits_tmpl>
-<%args>
-    BlockCache bc;
-</%args>
-<%java>
-  int hitPeriods = 0;
-  for(int i=0; i<bc.getStats().getNumPeriodsInWindow(); i++) {
-    if(bc.getStats().getWindowPeriods()[i] != null) {
-      hitPeriods++;
-    }
-  }
-</%java>
-<%if hitPeriods > 0 %>
-  <script>
-    // Wait for document to be fully loaded
-    document.addEventListener('DOMContentLoaded', function() {
-
-      // Count actual items in the DOM
-      const itemRows = document.querySelectorAll('tr.item-row');
-
-      // Pagination state
-      let currentPage = 1;
-      const pageSize = 10;
-      const totalItems = itemRows.length;
-      const totalPages = Math.ceil(totalItems / pageSize);
-
-      // Create page buttons
-      const pageButtonsContainer = document.getElementById('page-buttons');
-      if (pageButtonsContainer) {
-        for (let i = 1; i <= totalPages; i++) {
-          const button = document.createElement('button');
-          button.className = 'page-number';
-          button.textContent = i;
-          button.onclick = function() { goToPage(i); };
-          pageButtonsContainer.appendChild(button);
-        }
-      }
-      function displayItems() {
-        // Hide all item rows
-        itemRows.forEach(row => {
-          row.style.display = 'none';
-        });
-
-        // Calculate indexes
-        const startIndex = (currentPage - 1) * pageSize;
-        const endIndex = Math.min(startIndex + pageSize, totalItems);
-
-        // Show rows for current page
-        let displayedCount = 0;
-        for (let i = startIndex; i < endIndex; i++) {
-          const row = document.getElementById('row-' + i);
-          if (row) {
-            row.style.display = 'table-row';
-            displayedCount++;
-          }
-        }
-
-        // Update pagination UI
-        document.querySelectorAll('.page-number').forEach(btn => {
-          if (parseInt(btn.textContent) === currentPage) {
-            btn.classList.add('active');
-          } else {
-            btn.classList.remove('active');
-          }
-        });
-
-        const prevBtn = document.getElementById('prev-page');
-        const nextBtn = document.getElementById('next-page');
-
-        if (prevBtn) prevBtn.disabled = currentPage === 1;
-        if (nextBtn) nextBtn.disabled = currentPage === totalPages;
-
-        // Update page info
-        const pageInfo = document.getElementById('page-info');
-        if (pageInfo) {
-          pageInfo.textContent = `Showing ${startIndex + 1} to ${endIndex} of ${totalItems} items`;
-        }
-      }
-
-      function goToPage(page) {
-        if (page >= 1 && page <= totalPages) {
-          currentPage = page;
-          displayItems();
-        }
-      }
-
-      window.nextPage = function() {
-        goToPage(currentPage + 1);
-      };
-
-      window.prevPage = function() {
-        goToPage(currentPage - 1);
-      };
-
-      window.goToPage = goToPage;
-
-      // Check URL for initial page
-      const urlParams = new URLSearchParams(window.location.search);
-      const pageParam = urlParams.get('page');
-      if (pageParam) {
-        const parsedPage = parseInt(pageParam);
-        if (!isNaN(parsedPage) && parsedPage >= 1) {
-          currentPage = parsedPage;
-        }
-      }
-
-      // Initial display
-      displayItems();
-    });
-  </script>
-</%if>
-    <tr>
-        <td>Hits</td>
-        <td><% String.format("%,d", bc.getStats().getHitCount()) %></td>
-        <td>Number requests that were cache hits</td>
-    </tr>
-    <tr>
-        <td>Hits Caching</td>
-        <td><% String.format("%,d", bc.getStats().getHitCachingCount()) %></td>
-        <td>Cache hit block requests but only requests set to cache block if a miss</td>
-    </tr>
-    <tr>
-        <td>Misses</td>
-        <td><% String.format("%,d", bc.getStats().getMissCount()) %></td>
-        <td>Block requests that were cache misses but set to cache missed blocks</td>
-    </tr>
-    <tr>
-        <td>Misses Caching</td>
-        <td><% String.format("%,d", bc.getStats().getMissCachingCount()) %></td>
-        <td>Block requests that were cache misses but only requests set to use block cache</td>
-    </tr>
-    <tr>
-        <td>All Time Hit Ratio</td>
-        <td><% String.format("%,.2f", bc.getStats().getHitRatio() * 100) %><% "%" %></td>
-        <td>Hit Count divided by total requests count</td>
-    </tr>
-    <%for int i=0; i<hitPeriods; i++ %>
-      <tr id="row-<% i %>" class="item-row" style="display: none;">
-          <td>Hit Ratio for period starting at <% bc.getStats().getWindowPeriods()[i] %></td>
-          <%if bc.getStats().getRequestCounts()[i] > 0 %>
-            <td><% String.format("%,.2f", ((double)bc.getStats().getHitCounts()[i] / (double)bc.getStats().getRequestCounts()[i]) * 100.0) %><% "%" %></td>
-          <%else>
-            <td>No requests</td>
-          </%if>
-          <td>Hit Count divided by total requests count over the <% i %>th period of <% bc.getStats().getPeriodTimeInMinutes() %> minutes</td>
-      </tr>
-    </%for>
-    <%if hitPeriods > 0 %>
-      <tr class="pagination-row">
-        <td colspan="3">
-          <div class="pagination-container">
-            <button id="prev-page" onclick="prevPage()">Previous</button>
-            <span id="page-buttons" class="page-numbers">
-
-            </span>
-            <button id="next-page" onclick="nextPage()">Next</button>
-            <span id="page-info" class="page-info"></span>
-          </div>
-        </td>
-      </tr>
-    </%if>
-    <%if bc.getStats().getPeriodTimeInMinutes() > 0 %>
-      <tr>
-          <td>Last <% bc.getStats().getNumPeriodsInWindow()*bc.getStats().getPeriodTimeInMinutes() %> minutes Hit Ratio</td>
-          <td><% String.format("%,.2f", bc.getStats().getHitRatioPastNPeriods() * 100.0) %><% "%" %></td>
-          <td>Hit Count divided by total requests count for the last <% bc.getStats().getNumPeriodsInWindow()*bc.getStats().getPeriodTimeInMinutes() %> minutes</td>
-      </tr>
-        <style>
-          .pagination-container {
-            display: flex;
-            align-items: center;
-            gap: 8px;
-            padding: 10px 0;
-          }
-
-          .page-numbers {
-            display: flex;
-            gap: 4px;
-          }
-
-          .page-number {
-            min-width: 30px;
-            text-align: center;
-          }
-
-          .page-number.active {
-            font-weight: bold;
-            background-color: #eee;
-          }
-
-          .page-info {
-            margin-left: 15px;
-            font-size: 0.9em;
-            color: #666;
-          }
-        </style>
-    </%if>
-</%def>
-
-<%def bc_stats>
-<%args>
-    BlockCache bc;
-</%args>
-<%if bc == null %>
-<p>BlockCache is null</p>
-<%else>
-<table class="table table-striped">
-    <tr>
-        <th>Attribute</th>
-        <th>Value</th>
-        <th>Description</th>
-    </tr>
-    <tr>
-        <td>Size</td>
-        <td><% TraditionalBinaryPrefix.long2String(bc.getCurrentSize(),
-            "B", 1) %></td>
-        <td>Current size of block cache in use</td>
-    </tr>
-    <tr>
-        <td>Free</td>
-        <td><% TraditionalBinaryPrefix.long2String(bc.getFreeSize(),
-            "B", 1) %></td>
-        <td>The total free memory currently available to store more cache entries</td>
-    </tr>
-    <tr>
-        <td>Count</td>
-        <td><% String.format("%,d", bc.getBlockCount()) %></td>
-        <td>Number of blocks in block cache</td>
-    </tr>
-    <& evictions_tmpl; bc = bc; &>
-    <& hits_tmpl; bc = bc; &>
-</table>
-<p>If block cache is made up of more than one cache -- i.e. a L1 and a L2 -- then the above
-are combined counts. Request count is sum of hits and misses.</p>
-</%if>
-</%def>
-
-<%def bc_l>
-<%args>
-    BlockCache bc;
-    String name;
-    boolean evictions;
-</%args>
-<%if bc == null %>
-<p>No <% name %> deployed</p>
-<%else>
-<& block_cache; bc = bc; name = name; evictions = evictions;  &>
-</%if>
-</%def>
-
-<%def block_cache>
-<%args>
-    BlockCache bc;
-    String name;
-    boolean evictions;
-</%args>
-<%java>
-  String bcUrl = "http://hbase.apache.org/devapidocs/" + bc.getClass().getName().replaceAll("\\.", "/") + ".html";
-  String bcName = bc.getClass().getSimpleName();
-  int maxCachedBlocksByFile = BlockCacheUtil.getMaxCachedBlocksByFile(config);
-
-  boolean lru = bc instanceof LruBlockCache;
-
-  boolean bucketCache = bc.getClass().getSimpleName().equals("BucketCache");
-  BucketCacheStats bucketCacheStats = null;
-  BucketAllocator bucketAllocator = null;
-
-  if (bucketCache) {
-    bucketCacheStats = (BucketCacheStats)bc.getStats();
-    bucketAllocator = ((BucketCache)bc).getAllocator();
-  }
-</%java>
-<table id="blocks_summary" class="table table-striped">
-    <tr>
-        <th>Attribute</th>
-        <th>Value</th>
-        <th>Description</th>
-    </tr>
-    <tr>
-        <td>Implementation</td>
-        <td><a href="<% bcUrl %>"><% bc.getClass().getSimpleName() %></a></td>
-        <td>Class implementing this block cache Level</td>
-    </tr>
-<%if bucketCache %>
-    <tr>
-        <td>Implementation</td>
-        <td><% ((BucketCache)bc).getIoEngine() %></a></td>
-        <td>IOEngine</td>
-    </tr>
-</%if>
-    <tr>
-        <td>Cache Size Limit</td>
-        <td><% TraditionalBinaryPrefix.long2String(bc.getMaxSize(), "B", 1) %></td>
-        <td>Max size of cache</td>
-    </tr>
-    <tr>
-        <td>Block Count</td>
-        <td><% String.format("%,d", bc.getBlockCount()) %></td>
-        <td>Count of Blocks</td>
-    </tr>
-<%if !bucketCache %>
-    <tr>
-        <td>Data Block Count</td>
-        <td><% String.format("%,d", bc.getDataBlockCount()) %></td>
-        <td>Count of DATA Blocks</td>
-    </tr>
-</%if>
-<%if lru %>
-    <tr>
-        <td>Index Block Count</td>
-        <td><% String.format("%,d", ((LruBlockCache)bc).getIndexBlockCount()) %></td>
-        <td>Count of INDEX Blocks</td>
-    </tr>
-    <tr>
-        <td>Bloom Block Count</td>
-        <td><% String.format("%,d", ((LruBlockCache)bc).getBloomBlockCount()) %></td>
-        <td>Count of BLOOM Blocks</td>
-    </tr>
-</%if>
-      <tr>
-        <td>Size of Blocks</td>
-        <td><% TraditionalBinaryPrefix.long2String(bc.getCurrentSize(), "B", 1) %></td>
-        <td>Size of Blocks</td>
-    </tr>
-<%if !bucketCache %>
-    <tr>
-        <td>Size of Data Blocks</td>
-        <td><% TraditionalBinaryPrefix.long2String(bc.getCurrentDataSize(), "B", 1) %></td>
-        <td>Size of DATA Blocks</td>
-    </tr>
-</%if> 
-<%if lru %>
-    <tr>
-        <td>Size of Index Blocks</td>
-        <td><% TraditionalBinaryPrefix.long2String(((LruBlockCache)bc).getCurrentIndexSize(), "B", 1) %></td>
-        <td>Size of INDEX Blocks</td>
-    </tr>
-    <tr>
-        <td>Size of Bloom Blocks</td>
-        <td><% TraditionalBinaryPrefix.long2String(((LruBlockCache)bc).getCurrentBloomSize(), "B", 1) %></td>
-        <td>Size of BLOOM Blocks</td>
-    </tr>
-</%if>
- <& evictions_tmpl; bc = bc; &>
-<& hits_tmpl; bc = bc; &>
-
-<%if bucketCache %>
-    <tr>
-        <td>Hits per Second</td>
-        <td><% bucketCacheStats.getIOHitsPerSecond() %></td>
-        <td>Block gets against this cache per second</td>
-    </tr>
-    <tr>
-        <td>Time per Hit</td>
-        <td><% bucketCacheStats.getIOTimePerHit() %></td>
-        <td>Time per cache hit</td>
-    </tr>
-</%if>
-</table>
-
-<%doc>Call through to block cache Detail rendering template</%doc>
-<p>
-View block cache <a href="?format=json&bcn=<% name %>">as JSON</a> | Block cache <a href="?format=json&bcn=<% name %>&bcv=file">as JSON by file</a>
-<%if bc.getBlockCount() > maxCachedBlocksByFile %>
-<br>
-<b>Note</b>: JSON view of block cache will be incomplete, because block count <% bc.getBlockCount() %> is greater than <i>hbase.ui.blockcache.by.file.max</i> value of <% maxCachedBlocksByFile %>.
-Increase that value to get a complete picture.
-</%if>
-</p>
-</%def>
-
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/BlockCacheViewTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/BlockCacheViewTmpl.jamon
deleted file mode 100644
index ce55aaf1270..00000000000
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/BlockCacheViewTmpl.jamon
+++ /dev/null
@@ -1,80 +0,0 @@
-<%doc>
-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.
-
-
-This template is used to give views on an individual block cache as JSON.
-</%doc>
-<%args>
-CacheConfig cacheConfig;
-Configuration conf;
-String bcn;
-String bcv;
-BlockCache blockCache;
-</%args>
-<%import>
-java.util.*;
-org.apache.hadoop.conf.Configuration;
-org.apache.hadoop.hbase.io.hfile.BlockCacheUtil.CachedBlocksByFile;
-org.apache.hadoop.hbase.io.hfile.BlockCacheUtil;
-org.apache.hadoop.hbase.io.hfile.CachedBlock;
-org.apache.hadoop.hbase.io.hfile.CacheConfig;
-org.apache.hadoop.hbase.io.hfile.BlockCache;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketCacheStats;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketAllocator;
-org.apache.hadoop.hbase.io.hfile.bucket.BucketAllocator.Bucket;
-org.apache.hadoop.util.StringUtils;
-</%import>
-<%java>
-  BlockCache bc = blockCache;
-  BlockCache [] bcs = bc == null ? null : bc.getBlockCaches();
-  if (bcn.equals("L1")) {
-    bc = bcs == null || bcs.length == 0? bc: bcs[0];
-  } else {
-    if (bcs == null || bcs.length < 2) {
-      System.out.println("There is no L2 block cache");
-      return;
-    }
-    bc = bcs[1];
-  }
-  if (bc == null) {
-    System.out.println("There is no block cache");
-    return;
-  }
-  CachedBlocksByFile cbsbf = BlockCacheUtil.getLoadedCachedBlocksByFile(conf, bc);
-</%java>
-<%if bcv.equals("file") %><& bc_by_file; cbsbf = cbsbf; &><%else>[ <% BlockCacheUtil.toJSON(bc) %>, <% BlockCacheUtil.toJSON(cbsbf) %> ]</%if>
-<%java>
-cbsbf = null;
-</%java>
-
-<%def bc_by_file>
-<%args>
-    CachedBlocksByFile cbsbf;
-</%args>
-<%java>
-    boolean firstEntry = true;
-</%java>
-[<%for Map.Entry<String, NavigableSet<CachedBlock>> e: cbsbf.getCachedBlockStatsByFile().entrySet() %>
-  <%if !firstEntry %>,</%if><% BlockCacheUtil.toJSON(e.getKey(), e.getValue()) %>
-  <%java>
-      if (firstEntry) {
-        firstEntry = false;
-      }
-  </%java>
-</%for>]
-</%def>
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/RSStatusTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/RSStatusTmpl.jamon
deleted file mode 100644
index 15426675dec..00000000000
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/RSStatusTmpl.jamon
+++ /dev/null
@@ -1,330 +0,0 @@
-<%doc>
-
-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.
-</%doc>
-<%args>
-HRegionServer regionServer;
-String filter = "general";
-String format = "html";
-String bcn = "";
-String bcv = "";
-</%args>
-<%import>
-java.util.*;
-org.apache.hadoop.hbase.regionserver.HRegionServer;
-org.apache.hadoop.hbase.client.RegionInfo;
-org.apache.hadoop.hbase.ServerName;
-org.apache.hadoop.hbase.HBaseConfiguration;
-org.apache.hadoop.hbase.io.hfile.CacheConfig;
-org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
-org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ServerInfo;
-org.apache.hadoop.hbase.util.JvmVersion;
-org.apache.hadoop.hbase.zookeeper.MasterAddressTracker;
-</%import>
-<%doc>If json AND bcn is NOT an empty string presume it a block cache view request.</%doc>
-<%if format.equals("json") && bcn != null && bcn.length() > 0  %>
-  <& BlockCacheViewTmpl; conf = regionServer.getConfiguration(); cacheConfig = new CacheConfig(regionServer.getConfiguration()); bcn = bcn; bcv = bcv; blockCache = regionServer.getBlockCache().orElse(null)  &>
-  <%java return; %>
-<%elseif format.equals("json") %>
-  <& ../common/TaskMonitorTmpl; filter = filter; format = "json" &>
-  <%java return; %>
-</%if>
-<%java>
-  ServerInfo serverInfo = ProtobufUtil.getServerInfo(null, regionServer.getRSRpcServices());
-  ServerName serverName = ProtobufUtil.toServerName(serverInfo.getServerName());
-  List<RegionInfo> onlineRegions = ProtobufUtil.getOnlineRegions(regionServer.getRSRpcServices());
-  MasterAddressTracker masterAddressTracker = regionServer.getMasterAddressTracker();
-  ServerName masterServerName = masterAddressTracker == null ? null
-    : masterAddressTracker.getMasterAddress();
-  int infoPort = masterAddressTracker == null ? 0 : masterAddressTracker.getMasterInfoPort();
-</%java>
-
-<%class>
-  public String formatZKString() {
-    StringBuilder quorums = new StringBuilder();
-    String zkQuorum = regionServer.getZooKeeper().getQuorum();
-
-    if (null == zkQuorum) {
-      return quorums.toString();
-    }
-
-    String[] zks = zkQuorum.split(",");
-
-    if (zks.length == 0) {
-      return quorums.toString();
-    }
-
-    for(int i = 0; i < zks.length; ++i) {
-      quorums.append(zks[i].trim());
-
-      if (i != (zks.length - 1)) {
-        quorums.append("<br/>");
-      }
-    }
-
-    return quorums.toString();
-  }
-</%class>
-
-<!DOCTYPE html>
-<?xml version="1.0" encoding="UTF-8" ?>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <title>HBase Region Server: <% serverName.getHostname() %></title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <meta name="description" content="">
-
-    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
-    <link href="/static/css/hbase.css" rel="stylesheet">
-    <link rel="shortcut icon" href="/static/favicon.ico">
-  </head>
-
-  <body>
-
-    <nav class="navbar navbar-expand-md navbar-light fixed-top bg-light">
-      <div class="container-fluid">
-        <a class="navbar-brand" href="/rs-status">
-          <img src="/static/hbase_logo_small.png" alt="HBase Logo"/>
-        </a>
-        <button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
-          <span class="navbar-toggler-icon"></span>
-        </button>
-        <div class="collapse navbar-collapse">
-          <ul class="navbar-nav">
-            <li class="nav-item"><a class="nav-link active" href="/rs-status">Home</a></li>
-            <li class="nav-item"><a class="nav-link" href="/processRS.jsp">Process Metrics</a></li>
-            <li class="nav-item"><a class="nav-link" href="/logs/">Local Logs</a></li>
-            <li class="nav-item"><a class="nav-link" href="/rsOperationDetails.jsp">Operation Details</a></li>
-            <li class="nav-item"><a class="nav-link" href="/logLevel">Log Level</a></li>
-            <li class="nav-item"><a class="nav-link" href="/dump">Debug Dump</a></li>
-            <li class="nav-item dropdown">
-              <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                Metrics <span class="caret"></span>
-              </a>
-              <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
-                <a class="dropdown-item" target="_blank" href="/jmx">JMX</a>
-                <a class="dropdown-item" target="_blank" href="/jmx?description=true">JMX with description</a>
-                <a class="dropdown-item" target="_blank" href="/prometheus">Prometheus</a>
-                <a class="dropdown-item" target="_blank" href="/prometheus?description=true">Prometheus with description</a>
-              </div>
-            </li>
-            <li class="nav-item"><a class="nav-link" href="/prof">Profiler</a></li>
-            <%if HBaseConfiguration.isShowConfInServlet()%>
-            <li class="nav-item"><a class="nav-link" href="/conf">HBase Configuration</a></li>
-            </%if>
-          </ul>
-        </div><!--/.navbar-collapse -->
-      </div><!--/.container-fluid -->
-    </nav>
-
-  <div class="container-fluid content">
-    <div class="row inner_header">
-        <div class="page-header">
-            <h1>RegionServer <small><% serverName %></small></h1>
-        </div>
-    </div>
-    <div class="row">
-
-    <section>
-    <h2>Server Metrics</h2>
-    <& ServerMetricsTmpl; mWrap = regionServer.getMetrics().getRegionServerWrapper();
-      mServerWrap = regionServer.getRpcServer().getMetrics().getHBaseServerWrapper();
-      bbAllocator = regionServer.getRpcServer().getByteBuffAllocator(); &>
-    </section>
-
-    <section>
-    <h2>Block Cache</h2>
-    <& BlockCacheTmpl; cacheConfig = new CacheConfig(regionServer.getConfiguration()); config = regionServer.getConfiguration(); bc = regionServer.getBlockCache().orElse(null) &>
-    </section>
-
-    <section>
-    <& ../common/TaskMonitorTmpl; filter = filter; parent = "/rs-status" &>
-    </section>
-
-    <section>
-    <h2>Regions</h2>
-    <& RegionListTmpl; regionServer = regionServer; onlineRegions = onlineRegions; &>
-    </section>
-
-    <section>
-    <h2>Replication Status</h1>
-    <& ReplicationStatusTmpl; regionServer = regionServer; &>
-    </section>
-
-    <section>
-    <h2>Software Attributes</h2>
-    <table id="attributes_table" class="table table-striped">
-        <tr>
-            <th>Attribute Name</th>
-            <th>Value</th>
-            <th>Description</th>
-        </tr>
-        <tr>
-            <td>JVM Version</td>
-            <td><% JvmVersion.getVersion() %></td>
-            <td>JVM vendor and version</td>
-        </tr>
-        <tr>
-            <td>HBase Version</td>
-            <td><% org.apache.hadoop.hbase.util.VersionInfo.getVersion() %>, revision=<% org.apache.hadoop.hbase.util.VersionInfo.getRevision() %></td>
-            <td>HBase version and revision</td>
-        </tr>
-        <tr>
-            <td>HBase Compiled</td>
-            <td><% org.apache.hadoop.hbase.util.VersionInfo.getDate() %>, <% org.apache.hadoop.hbase.util.VersionInfo.getUser() %></td>
-            <td>When HBase version was compiled and by whom</td>
-        </tr>
-        <tr>
-            <td>HBase Source Checksum</td>
-            <td><% org.apache.hadoop.hbase.util.VersionInfo.getSrcChecksum() %></td>
-            <td>HBase source SHA512 checksum</td>
-        </tr>
-        <tr>
-            <td>Hadoop Version</td>
-            <td><% org.apache.hadoop.util.VersionInfo.getVersion() %>, revision=<% org.apache.hadoop.util.VersionInfo.getRevision() %></td>
-            <td>Hadoop version and revision</td>
-        </tr>
-        <tr>
-            <td>Hadoop Compiled</td>
-            <td><% org.apache.hadoop.util.VersionInfo.getDate() %>, <% org.apache.hadoop.util.VersionInfo.getUser() %></td>
-            <td>When Hadoop version was compiled and by whom</td>
-        </tr>
-        <tr>
-            <td>Hadoop Source Checksum</td>
-            <td><% org.apache.hadoop.util.VersionInfo.getSrcChecksum() %></td>
-            <td>Hadoop source MD5 checksum</td>
-        </tr>
-        <tr>
-            <td>ZooKeeper Client Version</td>
-            <td><% org.apache.zookeeper.Version.getVersion() %>, revision=<% org.apache.zookeeper.Version.getRevisionHash() %></td>
-            <td>ZooKeeper client version and revision hash</td>
-        </tr>
-        <tr>
-            <td>ZooKeeper Client Compiled</td>
-            <td><% org.apache.zookeeper.Version.getBuildDate() %></td>
-            <td>When ZooKeeper client version was compiled</td>
-        </tr>
-        <tr>
-            <td>ZooKeeper Quorum</td>
-            <%escape #n>
-            <td><% formatZKString() %></td>
-            <td>Addresses of all registered ZK servers</td>
-        </tr>
-        <tr>
-            <td>Coprocessors</td>
-            <td><% java.util.Arrays.toString(regionServer.getRegionServerCoprocessors()) %></td>
-            <td>Coprocessors currently loaded by this regionserver</td>
-        </tr>
-        <tr>
-            <td>RS Start Time</td>
-            <td><% new Date(regionServer.getStartcode()) %></td>
-            <td>Date stamp of when this region server was started</td>
-        </tr>
-        <tr>
-            <td>HBase Master</td>
-            <td>
-                <%if masterServerName == null %>
-                No master found
-                <%else>
-                <%java>
-                String host = masterServerName.getHostname() + ":" + infoPort;
-                String url = "//" + host + "/master.jsp";
-                </%java>
-                <a href="<% url %>"><% host %></a>
-                </%if>
-            </td>
-            <td>Address of HBase Master</td>
-        </tr>
-    </table>
-    </section>
-    </div>
-</div>
-<script src="/static/js/jquery.min.js" type="text/javascript"></script>
-<script src="/static/js/jquery.tablesorter.min.js" type="text/javascript"></script>
-<script src="/static/js/bootstrap.bundle.min.js" type="text/javascript"></script>
-<script src="/static/js/tab.js" type="text/javascript"></script>
-<script>
-$(document).ready(function()
-    {
-        $.tablesorter.addParser({
-            id: 'filesize',
-            is: function(s) {
-                return s.match(new RegExp( /([\.0-9]+)\ (B|KB|MB|GB|TB)/ ));
-            },
-            format: function(s) {
-                var suf = s.match(new RegExp( /(KB|B|GB|MB|TB)$/ ))[1];
-                var num = parseFloat(s.match( new RegExp( /([\.0-9]+)\ (B|KB|MB|GB|TB)/ ))[0]);
-                switch(suf) {
-                    case 'B':
-                        return num;
-                    case 'KB':
-                        return num * 1024;
-                    case 'MB':
-                        return num * 1024 * 1024;
-                    case 'GB':
-                        return num * 1024 * 1024 * 1024;
-                    case 'TB':
-                        return num * 1024 * 1024 * 1024 * 1024;
-                }
-            },
-            type: 'numeric'
-        });
-        $.tablesorter.addParser(
-        {
-            id: "separator",
-            is: function (s) {
-                return /^[0-9]?[0-9,]*$/.test(s);
-            },
-            format: function (s) {
-                return $.tablesorter.formatFloat( s.replace(/,/g,'') );
-            },
-            type: "numeric"
-        });
-
-        $("#baseStatsTable").tablesorter({
-            headers: {
-                '.cls_emptyMin': {empty: 'emptyMin'},
-                '.cls_emptyMax': {empty: 'emptyMax'}
-            }
-        });
-        $("#requestStatsTable").tablesorter({
-            headers: {
-                '.cls_separator': {sorter: 'separator'}
-            }
-        });
-        $("#storeStatsTable").tablesorter({
-            headers: {
-                '.cls_separator': {sorter: 'separator'},
-                '.cls_filesize': {sorter: 'filesize'}
-            }
-        });
-        $("#compactionStatsTable").tablesorter({
-            headers: {
-                '.cls_separator': {sorter: 'separator'}
-            }
-        });
-        $("#memstoreStatsTable").tablesorter({
-            headers: {
-                '.cls_filesize': {sorter: 'filesize'}
-            }
-        });
-    }
-);
-</script>
-</body>
-</html>
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/RegionListTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/RegionListTmpl.jamon
deleted file mode 100644
index c4b947308ba..00000000000
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/RegionListTmpl.jamon
+++ /dev/null
@@ -1,338 +0,0 @@
-<%doc>
-
-    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.
-</%doc>
-<%args>
-        HRegionServer regionServer;
-        List<RegionInfo> onlineRegions;
-</%args>
-<%import>
-        java.util.*;
-        org.apache.commons.lang3.time.FastDateFormat;
-        org.apache.hadoop.hbase.regionserver.HRegionServer;
-        org.apache.hadoop.hbase.util.Bytes;
-        org.apache.hadoop.hbase.client.RegionInfo;
-        org.apache.hadoop.hbase.client.RegionInfoDisplay;
-        org.apache.hadoop.hbase.regionserver.Region;
-        org.apache.hadoop.hbase.regionserver.HStoreFile;
-        org.apache.hadoop.hbase.regionserver.Store;
-        org.apache.hadoop.hbase.regionserver.StoreFile;
-        org.apache.hadoop.hbase.ServerName;
-        org.apache.hadoop.hbase.HBaseConfiguration;
-        org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ServerInfo;
-        org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.RegionLoad;
-        org.apache.hadoop.hbase.client.RegionReplicaUtil;
-        org.apache.hadoop.hbase.regionserver.MetricsRegionWrapper;
-        org.apache.hadoop.util.StringUtils;
-        org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
-</%import>
-<%if (onlineRegions != null && onlineRegions.size() > 0) %>
-
-    <%java>
-        Collections.sort(onlineRegions, RegionInfo.COMPARATOR);
-    </%java>
-
-    <div class="tabbable">
-        <ul class="nav nav-pills" role="tablist">
-            <li class="nav-item"><a class="nav-link active" href="#tab_regionBaseInfo" data-bs-toggle="tab" role="tab">Base Info</a> </li>
-            <li class="nav-item"><a class="nav-link" href="#tab_regionRequestStats" data-bs-toggle="tab" role="tab">Request metrics</a></li>
-            <li class="nav-item"><a class="nav-link" href="#tab_regionStoreStats" data-bs-toggle="tab" role="tab">Storefile Metrics</a></li>
-            <li class="nav-item"><a class="nav-link" href="#tab_regionMemstoreStats" data-bs-toggle="tab" role="tab">Memstore Metrics</a></li>
-            <li class="nav-item"><a class="nav-link" href="#tab_regionCompactStats" data-bs-toggle="tab" role="tab">Compaction Metrics</a></li>
-        </ul>
-        <div class="tab-content">
-            <div class="tab-pane active" id="tab_regionBaseInfo" role="tabpanel">
-                <& baseInfo; onlineRegions = onlineRegions; &>
-            </div>
-            <div class="tab-pane" id="tab_regionRequestStats" role="tabpanel">
-                <& requestStats; onlineRegions = onlineRegions; &>
-            </div>
-            <div class="tab-pane" id="tab_regionStoreStats" role="tabpanel">
-                <& storeStats; onlineRegions = onlineRegions; &>
-            </div>
-            <div class="tab-pane" id="tab_regionMemstoreStats" role="tabpanel">
-                <& memstoreStats; onlineRegions = onlineRegions; &>
-            </div>
-            <div class="tab-pane" id="tab_regionCompactStats" role="tabpanel">
-                <& compactStats; onlineRegions = onlineRegions; &>
-            </div>
-        </div>
-    </div>
-    <p>Region names are made of the containing table's name, a comma,
-    the start key, a comma, and a randomly generated region id.  To illustrate,
-    the region named
-    <em>domains,apache.org,5464829424211263407</em> is party to the table
-    <em>domains</em>, has an id of <em>5464829424211263407</em> and the first key
-    in the region is <em>apache.org</em>.  The <em>hbase:meta</em> 'table' is an internal
-    system table (or a 'catalog' table in db-speak).
-    The hbase:meta table keeps a list of all regions in the system. The empty key is used to denote
-    table start and table end.  A region with an empty start key is the first region in a table.
-    If a region has both an empty start key and an empty end key, it's the only region in the
-    table. See <a href="http://hbase.apache.org">HBase Home</a> for further explication.<p>
-<%else>
-    <p>Not serving regions</p>
-</%if>
-
-<%def baseInfo>
-<%args>
-    List<RegionInfo> onlineRegions;
-</%args>
-    <table id="baseStatsTable" class="tablesorter table table-striped">
-    <thead>
-        <tr>
-            <th>Region Name</th>
-            <th class="cls_emptyMin">Start Key</th>
-            <th class="cls_emptyMax">End Key</th>
-            <th>ReplicaID</th>
-        </tr>
-    </thead>
-
-    <tbody>
-        <%for RegionInfo r: onlineRegions %>
-        <tr>
-            <%java>
-             String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
-               regionServer.getConfiguration());
-            </%java>
-            <td><a href="region.jsp?name=<% r.getEncodedName() %>"><% displayName %></a></td>
-            <td><% Bytes.toStringBinary(RegionInfoDisplay.getStartKeyForDisplay(r,
-                                        regionServer.getConfiguration())) %></td>
-            <td><% Bytes.toStringBinary(RegionInfoDisplay.getEndKeyForDisplay(r,
-                                        regionServer.getConfiguration())) %></td>
-            <td><% r.getReplicaId() %></td>
-        </tr>
-        </%for>
-    </tbody>
-    </table>
-</%def>
-
-<%def requestStats>
-<%args>
-    List<RegionInfo> onlineRegions;
-</%args>
-    <table id="requestStatsTable" class="tablesorter table table-striped">
-    <thead>
-        <tr>
-            <th>Region Name</th>
-            <th class="cls_separator">Read Request Count</th>
-            <th class="cls_separator">Filtered Read Request Count</th>
-            <th class="cls_separator">Write Request Count</th>
-        </tr>
-    </thead>
-
-    <tbody>
-        <%for RegionInfo r: onlineRegions %>
-
-        <tr>
-        <%java>
-            RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
-            String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
-              regionServer.getConfiguration());
-        </%java>
-            <td><a href="region.jsp?name=<% r.getEncodedName() %>"><% displayName %></a></td>
-            <%if load != null %>
-            <td><% String.format("%,1d", load.getReadRequestsCount()) %></td>
-            <td><% String.format("%,1d", load.getFilteredReadRequestsCount()) %></td>
-            <td><% String.format("%,1d", load.getWriteRequestsCount()) %></td>
-            </%if>
-        </tr>
-        </%for>
-    </tbody>
-    </table>
-</%def>
-
-
-<%def storeStats>
-<%args>
-    List<RegionInfo> onlineRegions;
-</%args>
-    <table id="storeStatsTable" class="tablesorter table table-striped">
-    <thead>
-        <tr>
-            <th>Region Name</th>
-            <th class="cls_separator">Num. Stores</th>
-            <th class="cls_separator">Num. Storefiles</th>
-            <th class="cls_filesize">Storefile Size Uncompressed</th>
-            <th class="cls_filesize">Storefile Size</th>
-            <th class="cls_filesize">Index Size</th>
-            <th class="cls_filesize">Bloom Size</th>
-            <th>Data Locality</th>
-            <th>Len Of Biggest Cell</th>
-            <th>% Cached</th>
-        </tr>
-    </thead>
-
-    <tbody>
-        <%for RegionInfo r: onlineRegions %>
-
-        <tr>
-        <%java>
-            final String ZEROMB = "0 MB";
-            final String ZEROKB = "0 KB";
-            String uncompressedStorefileSizeStr = ZEROMB;
-            String storefileSizeStr = ZEROMB;
-            String indexSizeStr = ZEROKB;
-            String bloomSizeStr = ZEROKB;
-            RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
-            String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
-              regionServer.getConfiguration());
-             if (load != null) {
-             long uncompressedStorefileSize  = load.getStoreUncompressedSizeMB();
-             long storefileSize  = load.getStorefileSizeMB();
-             long indexSize  = load.getTotalStaticIndexSizeKB();
-             long bloomSize  = load.getTotalStaticBloomSizeKB();
-             if (uncompressedStorefileSize > 0) {
-                uncompressedStorefileSizeStr = TraditionalBinaryPrefix.long2String(
-                    uncompressedStorefileSize * TraditionalBinaryPrefix.MEGA.value, "B", 1);
-             }
-             if (storefileSize > 0) {
-                storefileSizeStr = TraditionalBinaryPrefix.long2String(storefileSize
-                             * TraditionalBinaryPrefix.MEGA.value, "B", 1);
-             }
-             if(indexSize > 0) {
-               indexSizeStr = TraditionalBinaryPrefix.long2String(indexSize
-                                              * TraditionalBinaryPrefix.KILO.value, "B", 1);
-             }
-             if (bloomSize > 0) {
-               bloomSizeStr = TraditionalBinaryPrefix.long2String(bloomSize
-                                              * TraditionalBinaryPrefix.KILO.value, "B", 1);
-              }
-           }
-           long lenOfBiggestCellInRegion = -1L;
-           Region region = regionServer.getRegion(r.getEncodedName());
-           if (region != null) {
-             List<? extends Store> stores = region.getStores();
-             for (Store store : stores) {
-               Collection<? extends StoreFile> storeFiles = store.getStorefiles();
-               for (StoreFile sf : storeFiles) {
-                 long lenOfBiggestCell = ((HStoreFile)sf).getFileInfo().getHFileInfo().getLenOfBiggestCell();
-                 if (lenOfBiggestCellInRegion < lenOfBiggestCell) {
-                   lenOfBiggestCellInRegion = lenOfBiggestCell;
-                 }
-               }
-             }
-           }
-
-        </%java>
-            <td><a href="region.jsp?name=<% r.getEncodedName() %>"><% displayName %></a></td>
-            <%if load != null %>
-            <td><% String.format("%,1d", load.getStores()) %></td>
-            <td><% String.format("%,1d", load.getStorefiles()) %></td>
-            <td><% uncompressedStorefileSizeStr %></td>
-            <td><% storefileSizeStr %></td>
-            <td><% indexSizeStr %></td>
-            <td><% bloomSizeStr %></td>
-            <td><% load.getDataLocality() %></td>
-            <td><% String.format("%,1d", lenOfBiggestCellInRegion) %></td>
-            <td><% StringUtils.formatPercent(load.getCurrentRegionCachedRatio(), 2) %></td>
-            </%if>
-        </tr>
-        </%for>
-    </tbody>
-    </table>
-</%def>
-
-
-<%def compactStats>
-<%args>
-    List<RegionInfo> onlineRegions;
-</%args>
-    <table id="compactionStatsTable" class="tablesorter table table-striped">
-    <thead>
-        <tr>
-            <th>Region Name</th>
-            <th class="cls_separator">Num. Compacting Cells</th>
-            <th class="cls_separator">Num. Compacted Cells</th>
-            <th>Compaction Progress</th>
-            <th data-date-format="yyyymmdd hhmm zz">Last Major Compaction</th>
-        </tr>
-    </thead>
-
-    <tbody>
-        <%for RegionInfo r: onlineRegions %>
-
-        <tr>
-        <%java>
-            RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
-            String percentDone = "";
-            String compactTime = "";
-            if  (load != null) {
-              if (load.getTotalCompactingKVs() > 0) {
-                percentDone = String.format("%.2f", 100 *
-                    ((float) load.getCurrentCompactedKVs() / load.getTotalCompactingKVs())) + "%";
-              }
-              if (load.getLastMajorCompactionTs() > 0) {
-                FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm (ZZ)");
-                compactTime = fdf.format(load.getLastMajorCompactionTs());
-              }
-            }
-            String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
-              regionServer.getConfiguration());
-        </%java>
-            <td><a href="region.jsp?name=<% r.getEncodedName() %>"><% displayName %></a></td>
-            <%if load != null %>
-            <td><% String.format("%,1d", load.getTotalCompactingKVs()) %></td>
-            <td><% String.format("%,1d", load.getCurrentCompactedKVs()) %></td>
-            <td><% percentDone %></td>
-            <td><% compactTime %></td>
-            </%if>
-        </tr>
-        </%for>
-    </tbody>
-    </table>
-</%def>
-
-<%def memstoreStats>
-<%args>
-    List<RegionInfo> onlineRegions;
-</%args>
-    <table id="memstoreStatsTable" class="tablesorter table table-striped">
-    <thead>
-        <tr>
-            <th>Region Name</th>
-            <th class="cls_filesize">Memstore Size</th>
-        </tr>
-    </thead>
-
-    <tbody>
-        <%for RegionInfo r: onlineRegions %>
-
-        <tr>
-        <%java>
-            final String ZEROMB = "0 MB";
-            String memStoreSizeMBStr = ZEROMB;
-            RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
-            String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
-              regionServer.getConfiguration());
-              if (load != null) {
-              long memStoreSizeMB = load.getMemStoreSizeMB();
-              if (memStoreSizeMB > 0) {
-              memStoreSizeMBStr = TraditionalBinaryPrefix.long2String(
-                         memStoreSizeMB * TraditionalBinaryPrefix.MEGA.value, "B", 1);
-               }
-             }
-        </%java>
-            <td><a href="region.jsp?name=<% r.getEncodedName() %>"><% displayName %></a></td>
-            <%if load != null %>
-            <td><% memStoreSizeMBStr %></td>
-            </%if>
-        </tr>
-        </%for>
-    </tbody>
-    </table>
-</%def>
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/ReplicationStatusTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/ReplicationStatusTmpl.jamon
deleted file mode 100644
index 273b26aecd3..00000000000
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/ReplicationStatusTmpl.jamon
+++ /dev/null
@@ -1,105 +0,0 @@
-<%doc>
-
-    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.
-</%doc>
-<%args>
-        HRegionServer regionServer;
-</%args>
-<%import>
-        java.util.*;
-        java.util.Map.Entry;
-        org.apache.hadoop.hbase.procedure2.util.StringUtils;
-        org.apache.hadoop.hbase.regionserver.HRegionServer;
-        org.apache.hadoop.hbase.replication.regionserver.ReplicationStatus;
-</%import>
-
-<%java>
-        Map<String, ReplicationStatus> walGroupsReplicationStatus = regionServer.getWalGroupsReplicationStatus();
-</%java>
-
-<%if (walGroupsReplicationStatus != null && walGroupsReplicationStatus.size() > 0) %>
-
-    <div class="tabbable">
-        <ul class="nav nav-pills" role="tablist">
-            <li class="nav-item"><a class="nav-link active" href="#tab_currentLog" data-bs-toggle="tab" role="tab">Current Log</a> </li>
-            <li class="nav-item"><a class="nav-link" href="#tab_replicationDelay" data-bs-toggle="tab" role="tab">Replication Delay</a></li>
-        </ul>
-        <div class="tab-content">
-            <div class="tab-pane active" id="tab_currentLog" role="tabpanel">
-                <& currentLog; metrics = walGroupsReplicationStatus; &>
-            </div>
-            <div class="tab-pane" id="tab_replicationDelay" role="tabpanel">
-                <& replicationDelay; metrics = walGroupsReplicationStatus; &>
-            </div>
-        </div>
-    </div>
-    <p> If the replication delay is UNKNOWN, that means this walGroup doesn't start replicate yet and it may get disabled.
-    If the size of log is 0, it means we are replicating current HLog, thus we can't get accurate size since it's not closed yet.</p>
-
-<%else>
-    <p>No Replication Metrics for Peers</p>
-</%if>
-
-<%def currentLog>
-<%args>
-    Map<String, ReplicationStatus> metrics;
-</%args>
-    <table class="table table-striped">
-        <tr>
-            <th>PeerId</th>
-            <th>WalGroup</th>
-            <th>Current Log</th>
-            <th>Size</th>
-            <th>Queue Size</th>
-            <th>Offset</th>
-        </tr>
-            <%for Map.Entry<String, ReplicationStatus> entry: metrics.entrySet() %>
-                 <tr>
-                     <td><% entry.getValue().getPeerId() %></td>
-                     <td><% entry.getValue().getWalGroup() %></td>
-                     <td><% entry.getValue().getCurrentPath() %> </td>
-                     <td><% StringUtils.humanSize(entry.getValue().getFileSize()) %></td>
-                     <td><% entry.getValue().getQueueSize() %></td>
-                     <td><% StringUtils.humanSize(entry.getValue().getCurrentPosition()) %></td>
-                 </tr>
-            </%for>
-    </table>
-</%def>
-
-<%def replicationDelay>
-<%args>
-    Map<String, ReplicationStatus> metrics;
-</%args>
-    <table class="table table-striped">
-        <tr>
-            <th>PeerId</th>
-            <th>WalGroup</th>
-            <th>Current Log</th>
-            <th>Last Shipped Age</th>
-            <th>Replication Delay</th>
-        </tr>
-            <%for Map.Entry<String, ReplicationStatus> entry: metrics.entrySet() %>
-                 <tr>
-                     <td><% entry.getValue().getPeerId() %></td>
-                     <td><% entry.getValue().getWalGroup() %></td>
-                     <td><% entry.getValue().getCurrentPath() %> </td>
-                     <td><% StringUtils.humanTimeDiff(entry.getValue().getAgeOfLastShippedOp()) %></td>
-                     <td><% entry.getValue().getReplicationDelay() == Long.MAX_VALUE ? "UNKNOWN" : StringUtils.humanTimeDiff(entry.getValue().getReplicationDelay()) %></td>
-                 </tr>
-            </%for>
-    </table>
-</%def>
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/ServerMetricsTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/ServerMetricsTmpl.jamon
deleted file mode 100644
index 7805a8b0908..00000000000
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/regionserver/ServerMetricsTmpl.jamon
+++ /dev/null
@@ -1,265 +0,0 @@
-<%doc>
-
-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.
-</%doc>
-<%args>
-MetricsRegionServerWrapper mWrap;
-MetricsHBaseServerWrapper mServerWrap;
-ByteBuffAllocator bbAllocator;
-</%args>
-<%import>
-java.util.*;
-org.apache.hadoop.hbase.regionserver.HRegionServer;
-org.apache.hadoop.hbase.io.ByteBuffAllocator;
-org.apache.hadoop.hbase.ipc.MetricsHBaseServerWrapper;
-org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapper;
-org.apache.hadoop.hbase.util.Bytes;
-org.apache.hadoop.hbase.ServerName;
-org.apache.hadoop.hbase.HBaseConfiguration;
-org.apache.hadoop.hbase.util.DirectMemoryUtils;
-org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
-java.lang.management.MemoryUsage;
-org.apache.hadoop.hbase.io.util.MemorySizeUtil;
-</%import>
-<div class="tabbable">
-    <ul class="nav nav-pills" role="tablist">
-        <li class="nav-item"><a class="nav-link active" href="#tab_baseStats" data-bs-toggle="tab" role="tab">Base Stats</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_memoryStats" data-bs-toggle="tab" role="tab">Memory</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_requestStats" data-bs-toggle="tab" role="tab">Requests</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_walStats" data-bs-toggle="tab" role="tab">WALs</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_storeStats" data-bs-toggle="tab" role="tab">Storefiles</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_queueStats" data-bs-toggle="tab" role="tab">Queues</a></li>
-        <li class="nav-item"><a class="nav-link" href="#tab_byteBuffAllocatorStats" data-bs-toggle="tab" role="tab">ByteBuffAllocator Stats</a></li>
-    </ul>
-    <div class="tab-content">
-        <div class="tab-pane active" id="tab_baseStats" role="tabpanel">
-            <& baseStats; mWrap = mWrap &>
-        </div>
-        <div class="tab-pane" id="tab_memoryStats" role="tabpanel">
-            <& memoryStats; mWrap = mWrap &>
-        </div>
-        <div class="tab-pane" id="tab_requestStats" role="tabpanel">
-            <& requestStats; mWrap = mWrap &>
-        </div>
-        <div class="tab-pane" id="tab_walStats" role="tabpanel">
-            <& walStats; mWrap = mWrap &>
-        </div>
-        <div class="tab-pane" id="tab_storeStats" role="tabpanel">
-            <& storeStats; mWrap = mWrap &>
-        </div>
-        <div class="tab-pane" id="tab_queueStats" role="tabpanel">
-            <& queueStats; mWrap = mWrap; mServerWrap = mServerWrap; &>
-        </div>
-        <div class="tab-pane" id="tab_byteBuffAllocatorStats" role="tabpanel">
-            <& byteBuffAllocatorStats; bbAllocator = bbAllocator; &>
-        </div>
-    </div>
-</div>
-
-<%def baseStats>
-<%args>
-    MetricsRegionServerWrapper mWrap;
-</%args>
-<table class="table table-striped">
-    <tr>
-
-        <th>Requests Per Second</th>
-        <th>Num. Regions</th>
-        <th>Block locality</th>
-        <th>Block locality (Secondary replicas)</th>
-        <th>Slow WAL Append Count</th>
-    </tr>
-    <tr>
-        <td><% String.format("%.0f", mWrap.getRequestsPerSecond()) %></td>
-        <td><% mWrap.getNumOnlineRegions() %></td>
-        <td><% String.format("%.3f",mWrap.getPercentFileLocal()) %><% "%" %></td>
-        <td><% String.format("%.3f",mWrap.getPercentFileLocalSecondaryRegions()) %><% "%" %></td>
-        <td><% mWrap.getNumWALSlowAppend() %></td>
-    </tr>
-</table>
-</%def>
-
-<%def memoryStats>
-<%args>
-MetricsRegionServerWrapper mWrap;
-</%args>
-<%java
-  long usedHeap = -1L;
-  long maxHeap = -1L;
-  final MemoryUsage usage = MemorySizeUtil.safeGetHeapMemoryUsage();
-  if (usage != null) {
-    maxHeap = usage.getMax();
-    usedHeap = usage.getUsed();
-  }
-%>
-<table class="table table-striped">
-<tr>
-    <tr>
-        <th>Used Heap</th>
-        <th>Max Heap</th>
-        <th>Direct Memory Used</th>
-        <th>Direct Memory Configured</th>
-        <th>Memstore On-Heap Size / Limit</th>
-        <th>Memstore Off-Heap Size / Limit</th>
-        <th>Memstore Data Size (On&&Off Heap)</th>
-    </tr>
-</tr>
-<tr>
-    <td>
-        <% TraditionalBinaryPrefix.long2String(usedHeap, "B", 1) %>
-    </td>
-    <td>
-        <% TraditionalBinaryPrefix.long2String(maxHeap, "B", 1) %>
-    </td>
-    <td>
-        <% TraditionalBinaryPrefix.long2String(DirectMemoryUtils.getDirectMemoryUsage(), "B", 1) %>
-    </td>
-    <td>
-        <% TraditionalBinaryPrefix.long2String(DirectMemoryUtils.getDirectMemorySize(), "B", 1) %>
-    </td>
-    <td>
-        <% TraditionalBinaryPrefix.long2String(mWrap.getOnHeapMemStoreSize(), "B", 1) + " / "
-         + TraditionalBinaryPrefix.long2String(mWrap.getOnHeapMemStoreLimit(), "B", 1) %>
-    </td>
-    <td>
-        <% TraditionalBinaryPrefix.long2String(mWrap.getOffHeapMemStoreSize(), "B", 1) + " / "
-         + TraditionalBinaryPrefix.long2String(mWrap.getOffHeapMemStoreLimit(), "B", 1) %>
-    </td>
-    <td>
-        <% TraditionalBinaryPrefix.long2String(mWrap.getMemStoreSize(), "B", 1) %>
-    </td>
-</tr>
-</table>
-</%def>
-
-<%def walStats>
-<%args>
-MetricsRegionServerWrapper mWrap;
-</%args>
-<table class="table table-striped">
-<tr>
-    <tr>
-        <th>Num. WAL Files</th>
-        <th>Size. WAL Files</th>
-        <th>WAL exclude DNs</th>
-    </tr>
-</tr>
-<tr>
-    <td><% mWrap.getNumWALFiles() %></td>
-    <td><% TraditionalBinaryPrefix.long2String(mWrap.getWALFileSize(), "B", 1) %></td>
-    <td>
-        <%for String exclude: mWrap.getWALExcludeDNs() %>
-            <% exclude %><br>
-        </%for>
-    </td>
-</tr>
-</table>
-</%def>
-
-<%def storeStats>
-<%args>
-MetricsRegionServerWrapper mWrap;
-</%args>
-<table class="table table-striped">
-<tr>
-
-    <th>Num. Stores</th>
-    <th>Num. Storefiles</th>
-    <th>Root Index Size</th>
-    <th>Index Size</th>
-    <th>Bloom Size</th>
-</tr>
-<tr>
-    <td><% mWrap.getNumStores() %></td>
-    <td><% mWrap.getNumStoreFiles() %></td>
-    <td><% TraditionalBinaryPrefix.long2String(mWrap.getStoreFileIndexSize(), "B", 1) %></td>
-    <td><% TraditionalBinaryPrefix.long2String(mWrap.getTotalStaticIndexSize(), "B", 1) %></td>
-    <td><% TraditionalBinaryPrefix.long2String(mWrap.getTotalStaticBloomSize(), "B", 1) %></td>
-</tr>
-</table>
-</%def>
-
-
-<%def requestStats>
-<%args>
-MetricsRegionServerWrapper mWrap;
-</%args>
-<table class="table table-striped">
-<tr>
-    <th>Request Per Second</th>
-    <th>Read Request Count</th>
-    <th>Filtered Read Request Count</th>
-    <th>Write Request Count</th>
-</tr>
-<tr>
-    <td><% String.format("%.0f", mWrap.getRequestsPerSecond()) %></td>
-    <td><% mWrap.getReadRequestsCount() %></td>
-    <td><% mWrap.getFilteredReadRequestsCount() %></td>
-    <td><% mWrap.getWriteRequestsCount() %></td>
-</tr>
-</table>
-</%def>
-
-<%def queueStats>
-<%args>
-MetricsRegionServerWrapper mWrap;
-MetricsHBaseServerWrapper mServerWrap;
-</%args>
-<table class="table table-striped">
-<tr>
-    <th>Compaction Queue Length</th>
-    <th>Flush Queue Length</th>
-    <th>Priority Call Queue Length</th>
-    <th>General Call Queue Length</th>
-    <th>Replication Call Queue Length</th>
-    <th>Total Call Queue Size</th>
-
-</tr>
-<tr>
-    <td><% mWrap.getCompactionQueueSize() %></td>
-    <td><% mWrap.getFlushQueueSize() %></td>
-    <td><% mServerWrap.getPriorityQueueLength() %></td>
-    <td><% mServerWrap.getGeneralQueueLength() %></td>
-    <td><% mServerWrap.getReplicationQueueLength() %></td>
-    <td><% TraditionalBinaryPrefix.long2String(mServerWrap.getTotalQueueSize(), "B", 1) %></td>
-</tr>
-</table>
-</%def>
-
-<%def byteBuffAllocatorStats>
-<%args>
-ByteBuffAllocator bbAllocator;
-</%args>
-<table class="table table-striped">
-<tr>
-    <th>Total Heap Allocation</th>
-    <th>Total Pool Allocation</th>
-    <th>Heap Allocation Ratio</th>
-    <th>Total Buffer Count</th>
-    <th>Used Buffer Count</th>
-    <th>Buffer Size</th>
-</tr>
-<tr>
-    <td><% TraditionalBinaryPrefix.long2String(ByteBuffAllocator.getHeapAllocationBytes(bbAllocator, ByteBuffAllocator.HEAP), "B", 1) %></td>
-    <td><% TraditionalBinaryPrefix.long2String(bbAllocator.getPoolAllocationBytes(), "B", 1) %></td>
-    <td><% String.format("%.3f", ByteBuffAllocator.getHeapAllocationRatio(bbAllocator, ByteBuffAllocator.HEAP) * 100) %><% "%" %></td>
-    <td><% bbAllocator.getTotalBufferCount() %></td>
-    <td><% bbAllocator.getUsedBufferCount() %></td>
-    <td><% TraditionalBinaryPrefix.long2String(bbAllocator.getBufferSize(), "B", 1) %></td>
-</tr>
-</table>
-</%def>
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/MasterStatusUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/MasterStatusUtil.java
index 221e43f8e11..c860e89681c 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/MasterStatusUtil.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/http/MasterStatusUtil.java
@@ -79,7 +79,7 @@ public final class MasterStatusUtil {
 
   public static String serverNameLink(HMaster master, ServerName serverName) {
     int infoPort = master.getRegionServerInfoPort(serverName);
-    String url = "//" + serverName.getHostname() + ":" + infoPort + "/rs-status";
+    String url = "//" + serverName.getHostname() + ":" + infoPort + "/regionserver.jsp";
     if (infoPort > 0) {
       return "<a href=\"" + url + "\">" + serverName.getServerName() + "</a>";
     } else {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/http/RSStatusConstants.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/http/RSStatusConstants.java
new file mode 100644
index 00000000000..09b7c34e331
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/http/RSStatusConstants.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.regionserver.http;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * Constants used by the web UI JSP pages.
+ */
+@InterfaceAudience.Private
+public final class RSStatusConstants {
+  public static final String FILTER = "filter";
+  public static final String FILTER_GENERAL = "general";
+  public static final String FORMAT = "format";
+  public static final String FORMAT_JSON = "json";
+  public static final String FORMAT_HTML = "html";
+  public static final String PARENT = "parent";
+  public static final String BLOCK_CACHE_NAME = "bcn";
+  public static final String BLOCK_CACHE_NAME_L1 = "L1";
+  public static final String BLOCK_CACHE_V = "bcv";
+  public static final String BLOCK_CACHE_V_FILE = "file";
+
+  private RSStatusConstants() {
+    // Do not instantiate.
+  }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/http/RSStatusServlet.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/http/RSStatusServlet.java
index 43bac8791b4..9b8f9833f0b 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/http/RSStatusServlet.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/http/RSStatusServlet.java
@@ -18,43 +18,20 @@
 package org.apache.hadoop.hbase.regionserver.http;
 
 import java.io.IOException;
-import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.apache.hadoop.hbase.regionserver.HRegionServer;
-import org.apache.hadoop.hbase.tmpl.regionserver.RSStatusTmpl;
 import org.apache.yetus.audience.InterfaceAudience;
 
+/**
+ * Only kept for redirecting to regionserver.jsp.
+ */
 @InterfaceAudience.Private
 public class RSStatusServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;
 
   @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-    throws ServletException, IOException {
-    HRegionServer hrs =
-      (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
-    assert hrs != null : "No RS in context!";
-
-    String format = req.getParameter("format");
-    if ("json".equals(format)) {
-      resp.setContentType("application/json");
-    } else {
-      resp.setContentType("text/html");
-    }
-
-    if (!hrs.isOnline()) {
-      resp.getWriter().write("The RegionServer is initializing!");
-      resp.getWriter().close();
-      return;
-    }
-
-    RSStatusTmpl tmpl = new RSStatusTmpl();
-    if (format != null) tmpl.setFormat(format);
-    if (req.getParameter("filter") != null) tmpl.setFilter(req.getParameter("filter"));
-    if (req.getParameter("bcn") != null) tmpl.setBcn(req.getParameter("bcn"));
-    if (req.getParameter("bcv") != null) tmpl.setBcv(req.getParameter("bcv"));
-    tmpl.render(resp.getWriter(), hrs);
+  public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    response.sendRedirect(request.getContextPath() + "/regionserver.jsp");
   }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ZKStringFormatter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ZKStringFormatter.java
new file mode 100644
index 00000000000..a83f07014b0
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ZKStringFormatter.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.util;
+
+import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * Utility used by both Master and Region Server web UI JSP pages.
+ */
+@InterfaceAudience.Private
+public final class ZKStringFormatter {
+
+  private ZKStringFormatter() {
+    // Do not instantiate.
+  }
+
+  public static String formatZKString(ZKWatcher zookeeper) {
+    StringBuilder quorums = new StringBuilder();
+    String zkQuorum = zookeeper.getQuorum();
+
+    if (null == zkQuorum) {
+      return quorums.toString();
+    }
+
+    String[] zks = zkQuorum.split(",");
+
+    if (zks.length == 0) {
+      return quorums.toString();
+    }
+
+    for (int i = 0; i < zks.length; ++i) {
+      quorums.append(zks[i].trim());
+
+      if (i != (zks.length - 1)) {
+        quorums.append("<br/>");
+      }
+    }
+
+    return quorums.toString();
+  }
+}
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/taskMonitor.jsp b/hbase-server/src/main/resources/hbase-webapps/common/taskMonitor.jsp
similarity index 100%
rename from hbase-server/src/main/resources/hbase-webapps/master/taskMonitor.jsp
rename to hbase-server/src/main/resources/hbase-webapps/common/taskMonitor.jsp
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/taskMonitorRenderTasks.jsp b/hbase-server/src/main/resources/hbase-webapps/common/taskMonitorRenderTasks.jsp
similarity index 100%
rename from hbase-server/src/main/resources/hbase-webapps/master/taskMonitorRenderTasks.jsp
rename to hbase-server/src/main/resources/hbase-webapps/common/taskMonitorRenderTasks.jsp
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/hbck.jsp b/hbase-server/src/main/resources/hbase-webapps/master/hbck.jsp
index a6c6c2d17e6..210438ba7fe 100644
--- a/hbase-server/src/main/resources/hbase-webapps/master/hbck.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/master/hbck.jsp
@@ -383,7 +383,7 @@ private static String formatServerName(HMaster master,
     int infoPort = master.getRegionServerInfoPort(serverName);
     if (infoPort > 0) {
       return "<a href=" + "//" + serverName.getHostname() + ":" +
-        infoPort + "/rs-status>" + sn + "</a>";
+        infoPort + "/regionserver.jsp>" + sn + "</a>";
     } else {
       return "<b>" + sn + "</b>";
     }
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/master.jsp b/hbase-server/src/main/resources/hbase-webapps/master/master.jsp
index 7e537123851..76dbd656e9d 100644
--- a/hbase-server/src/main/resources/hbase-webapps/master/master.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/master/master.jsp
@@ -32,6 +32,7 @@
   if (format != null && format.equals("json")) {
     request.setAttribute(MasterStatusConstants.FILTER, filter);
     request.setAttribute(MasterStatusConstants.FORMAT, "json");
+    response.setContentType("application/json");
   %>
   <jsp:include page="taskMonitor.jsp"/>
 <%
@@ -143,6 +144,7 @@
 <% } %>
 
   <section>
+    <% request.setAttribute(MasterStatusConstants.PARENT, "/master.jsp"); %>
     <jsp:include page="taskMonitor.jsp"/>
   </section>
 
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp b/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp
index c86c9902ea1..91734e8d0a3 100644
--- a/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp
@@ -38,10 +38,11 @@
   import="org.apache.hadoop.hbase.rsgroup.RSGroupUtil"
   import="org.apache.hadoop.hbase.util.Bytes"
   import="org.apache.hadoop.hbase.util.VersionInfo"
-  import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix"%>
-<%@ page import="org.apache.hadoop.hbase.ServerMetrics" %>
-<%@ page import="org.apache.hadoop.hbase.Size" %>
-<%@ page import="org.apache.hadoop.hbase.RegionMetrics" %>
+  import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix"
+  import="org.apache.hadoop.hbase.ServerMetrics"
+  import="org.apache.hadoop.hbase.Size"
+  import="org.apache.hadoop.hbase.RegionMetrics"
+  import="static org.apache.hadoop.hbase.master.http.MasterStatusUtil.serverNameLink" %>
 <%
   String rsGroupName = request.getParameter("name");
   pageContext.setAttribute("pageTitle", "RSGroup: " + rsGroupName);
@@ -167,11 +168,9 @@
                      totalRequestsPerSecond += sl.getRequestCountPerSecond();
                      lastContact = (System.currentTimeMillis() - sl.getReportTimestamp())/1000;
                    }
-                   long startcode = serverName.getStartCode();
-                   int infoPort = master.getRegionServerInfoPort(serverName);
-                   String url = "//" + serverName.getHostname() + ":" + infoPort + "/rs-status";%>
+                   long startcode = serverName.getStartCode(); %>
                    <tr>
-                     <td><a href="<%= url %>"><%= serverName.getServerName() %></a></td>
+                     <td><%= serverNameLink(master, serverName) %></td>
                      <td><%= new Date(startcode) %></td>
                      <td><%= lastContact %></td>
                      <td><%= version %></td>
@@ -223,8 +222,6 @@
                    double memStoreSizeMB = sl.getRegionMetrics().values()
                            .stream().mapToDouble(rm -> rm.getMemStoreSize().get(Size.Unit.MEGABYTE))
                            .sum();
-                   int infoPort = master.getRegionServerInfoPort(serverName);
-                   String url = "//" + serverName.getHostname() + ":" + infoPort + "/rs-status";
 
                    if (memStoreSizeMB > 0) {
                      memStoreSizeMBStr = TraditionalBinaryPrefix.long2String(
@@ -240,7 +237,7 @@
                    }
             %>
                    <tr>
-                     <td><a href="<%= url %>"><%= serverName.getServerName() %></a></td>
+                     <td><%= serverNameLink(master, serverName) %></td>
                      <td><%= usedHeapSizeMBStr %></td>
                      <td><%= maxHeapSizeMBStr %></td>
                      <td><%= memStoreSizeMBStr %></td>
@@ -268,17 +265,15 @@
                  ServerName serverName = serverMaping.get(server);
                  ServerMetrics sl = onlineServers.get(server);
                  if (sl != null && serverName != null) {
-                   int infoPort = master.getRegionServerInfoPort(serverName);
                    long readRequestCount = 0;
                    long writeRequestCount = 0;
                    for (RegionMetrics rm : sl.getRegionMetrics().values()) {
                      readRequestCount += rm.getReadRequestCount();
                      writeRequestCount += rm.getWriteRequestCount();
                    }
-                   String url = "//" + serverName.getHostname() + ":" + infoPort + "/rs-status";
             %>
                    <tr>
-                     <td><a href="<%= url %>"><%= serverName.getServerName() %></a></td>
+                     <td><%= serverNameLink(master, serverName) %></td>
                      <td><%= sl.getRequestCountPerSecond() %></td>
                      <td><%= readRequestCount %></td>
                      <td><%= writeRequestCount %></td>
@@ -328,8 +323,6 @@
                       totalStaticIndexSizeKB += rm.getStoreFileUncompressedDataIndexSize().get(Size.Unit.KILOBYTE);
                       totalStaticBloomSizeKB += rm.getBloomFilterSize().get(Size.Unit.KILOBYTE);
                     }
-                    int infoPort = master.getRegionServerInfoPort(serverName);
-                    String url = "//" + serverName.getHostname() + ":" + infoPort + "/rs-status";
                     if (storeUncompressedSizeMB > 0) {
                       storeUncompressedSizeMBStr = TraditionalBinaryPrefix.long2String(
                       (long) storeUncompressedSizeMB * TraditionalBinaryPrefix.MEGA.value, "B", 1);
@@ -348,7 +341,7 @@
                     }
             %>
                     <tr>
-                      <td><a href="<%= url %>"><%= serverName.getServerName() %></a></td>
+                      <td><%= serverNameLink(master, serverName) %></td>
                       <td><%= storeCount %></td>
                       <td><%= storeFileCount %></td>
                       <td><%= storeUncompressedSizeMBStr %></td>
@@ -394,11 +387,9 @@
                          percentDone = String.format("%.2f", 100 *
                             ((float) currentCompactedCells / totalCompactingCells)) + "%";
                     }
-                    int infoPort = master.getRegionServerInfoPort(serverName);
-                    String url = "//" + serverName.getHostname() + ":" + infoPort + "/rs-status";
             %>
                     <tr>
-                      <td><a href="<%= url %>"><%= serverName.getServerName() %></a></td>
+                      <td><%= serverNameLink(master, serverName) %></td>
                       <td><%= totalCompactingCells %></td>
                       <td><%= currentCompactedCells %></td>
                       <td><%= totalCompactingCells - currentCompactedCells %></td>
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/softwareAttributes.jsp b/hbase-server/src/main/resources/hbase-webapps/master/softwareAttributes.jsp
index 725a116790c..3feb1e1323f 100644
--- a/hbase-server/src/main/resources/hbase-webapps/master/softwareAttributes.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/master/softwareAttributes.jsp
@@ -23,34 +23,8 @@
          import="org.apache.hadoop.hbase.util.JvmVersion"
          import="org.apache.hadoop.hbase.util.CommonFSUtils"
          import="org.apache.hadoop.util.StringUtils"
-         import="org.apache.hadoop.hbase.master.http.MasterStatusConstants" %>
-
-<%!
-  public static String formatZKString(HMaster master) {
-    StringBuilder quorums = new StringBuilder();
-    String zkQuorum = master.getZooKeeper().getQuorum();
-
-    if (null == zkQuorum) {
-      return quorums.toString();
-    }
-
-    String[] zks = zkQuorum.split(",");
-
-    if (zks.length == 0) {
-      return quorums.toString();
-    }
-
-    for(int i = 0; i < zks.length; ++i) {
-      quorums.append(zks[i].trim());
-
-      if (i != (zks.length - 1)) {
-        quorums.append("<br/>");
-      }
-    }
-
-    return quorums.toString();
-  }
-%>
+         import="org.apache.hadoop.hbase.master.http.MasterStatusConstants"
+         import="org.apache.hadoop.hbase.util.ZKStringFormatter" %>
 
 <%
   HMaster master = (HMaster) getServletContext().getAttribute(HMaster.MASTER);
@@ -111,7 +85,7 @@
   </tr>
   <tr>
     <td>ZooKeeper Quorum</td>
-      <td> <%= formatZKString(master) %> </td>
+      <td> <%= ZKStringFormatter.formatZKString(master.getZooKeeper()) %> </td>
     <td>Addresses of all registered ZK servers. For more, see <a href="/zk.jsp">zk dump</a>.</td>
   </tr>
   <tr>
@@ -121,7 +95,7 @@
   </tr>
   <tr>
     <td>Cluster Key</td>
-    <td> <%= formatZKString(master) %>:<%= master.getZooKeeper().getZNodePaths().baseZNode %></td>
+    <td> <%= ZKStringFormatter.formatZKString(master.getZooKeeper()) %>:<%= master.getZooKeeper().getZNodePaths().baseZNode %></td>
     <td>Key to add this cluster as a peer for replication. Use 'help "add_peer"' in the shell for details.</td>
   </tr>
   <tr>
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp
index 9a6aec049a7..d88d968e199 100644
--- a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp
@@ -124,7 +124,7 @@
     String hostNameEncoded = URLEncoder.encode(hostName, StandardCharsets.UTF_8);
     // This port might be wrong if RS actually ended up using something else.
     int serverInfoPort = master.getRegionServerInfoPort(serverName);
-    String urlRegionServer = "//" + hostNameEncoded + ":" + serverInfoPort + "/rs-status";
+    String urlRegionServer = "//" + hostNameEncoded + ":" + serverInfoPort + "/regionserver.jsp";
 
     return "<td><a href=\"" + urlRegionServer + "\">" + StringEscapeUtils.escapeHtml4(hostName)
       + ":" + serverInfoPort + "</a></td>";
@@ -398,7 +398,7 @@
                 %>
               <tr>
                 <td><%= escapeXml(meta.getRegionNameAsString()) %></td>
-                <td><a href="http://<%= hostAndPort %>/rs-status"><%= StringEscapeUtils.escapeHtml4(hostAndPort) %></a></td>
+                <td><a href="http://<%= hostAndPort %>/regionserver.jsp"><%= StringEscapeUtils.escapeHtml4(hostAndPort) %></a></td>
                 <td><%= readReq%></td>
                 <td><%= writeReq%></td>
                 <td><%= fileSizeUncompressed%></td>
@@ -455,7 +455,7 @@
                  %>
                <tr>
                  <td><%= escapeXml(meta.getRegionNameAsString()) %></td>
-                 <td><a href="http://<%= hostAndPort %>/rs-status"><%= StringEscapeUtils.escapeHtml4(hostAndPort) %></a></td>
+                 <td><a href="http://<%= hostAndPort %>/regionserver.jsp"><%= StringEscapeUtils.escapeHtml4(hostAndPort) %></a></td>
                  <td><%= locality%></td>
                  <td><%= localityForSsd%></td>
                </tr>
@@ -512,7 +512,7 @@
             %>
               <tr>
                 <td><%= escapeXml(meta.getRegionNameAsString()) %></td>
-                <td><a href="http://<%= hostAndPort %>/rs-status"><%= StringEscapeUtils.escapeHtml4(hostAndPort) %></a></td>
+                <td><a href="http://<%= hostAndPort %>/regionserver.jsp"><%= StringEscapeUtils.escapeHtml4(hostAndPort) %></a></td>
                 <td><%= String.format("%,1d", compactingCells)%></td>
                 <td><%= String.format("%,1d", compactedCells)%></td>
                 <td><%= String.format("%,1d", compactingCells - compactedCells)%></td>
@@ -1174,7 +1174,7 @@
     for (Map.Entry<ServerName, Integer> rdEntry : regDistribution.entrySet()) {
       ServerName addr = rdEntry.getKey();
       String url = "//" + URLEncoder.encode(addr.getHostname(), StandardCharsets.UTF_8) + ":"
-        + master.getRegionServerInfoPort(addr) + "/rs-status";
+        + master.getRegionServerInfoPort(addr) + "/regionserver.jsp";
   %>
       <tr>
         <td><a href="<%= url %>"><%= StringEscapeUtils.escapeHtml4(addr.getHostname())
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCache.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCache.jsp
new file mode 100644
index 00000000000..7bba000a271
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCache.jsp
@@ -0,0 +1,72 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCache"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer" %>
+<%@ page import="org.apache.hadoop.hbase.io.hfile.CacheConfig" %>
+
+<%-- Template for rendering Block Cache tabs in RegionServer Status page. --%>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  CacheConfig cacheConfig = new CacheConfig(regionServer.getConfiguration());
+
+  BlockCache bc = regionServer.getBlockCache().orElse(null);
+
+  BlockCache[] bcs = bc == null ? null : bc.getBlockCaches();
+  BlockCache l1 = bcs == null ? bc : bcs[0];
+  BlockCache l2 = bcs == null ? null : bcs.length <= 1 ? null : bcs[1];
+%>
+
+<div class="tabbable">
+  <ul class="nav nav-pills" role="tablist">
+    <li class="nav-item"><a class="nav-link active" href="#tab_bc_baseInfo" data-bs-toggle="tab" role="tab">Base Info</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_bc_config" data-bs-toggle="tab" role="tab">Config</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_bc_stats" data-bs-toggle="tab" role="tab">Stats</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_bc_l1" data-bs-toggle="tab" role="tab">L1</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_bc_l2" data-bs-toggle="tab" role="tab">L2</a></li>
+  </ul>
+  <div class="tab-content">
+    <div class="tab-pane active" id="tab_bc_baseInfo" role="tabpanel">
+      <% request.setAttribute("bc", bc); %>
+      <jsp:include page="blockCacheBaseInfo.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_bc_config" role="tabpanel">
+      <% request.setAttribute("cacheConfig", cacheConfig); %>
+      <jsp:include page="blockCacheConfig.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_bc_stats" role="tabpanel">
+      <% request.setAttribute("bc", bc); %>
+      <jsp:include page="blockCacheStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_bc_l1" role="tabpanel">
+      <% request.setAttribute("bc", l1); %>
+      <% request.setAttribute("name", "L1"); %>
+      <jsp:include page="blockCacheLevel.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_bc_l2" role="tabpanel">
+      <% request.setAttribute("bc", l2); %>
+      <% request.setAttribute("name", "L2"); %>
+      <jsp:include page="blockCacheLevel.jsp"/>
+    </div>
+  </div>
+</div>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionserver.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheBaseInfo.jsp
similarity index 51%
copy from hbase-server/src/main/resources/hbase-webapps/regionserver/regionserver.jsp
copy to hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheBaseInfo.jsp
index ee63075d5d6..d7d322d4754 100644
--- a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionserver.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheBaseInfo.jsp
@@ -17,4 +17,26 @@
  * limitations under the License.
  */
 --%>
-<meta HTTP-EQUIV="REFRESH" content="0;url=/rs-status"/>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCache" %>
+
+<%
+  BlockCache bc = (BlockCache) request.getAttribute("bc");
+
+  String bcUrl = bc == null ? null : "http://hbase.apache.org/devapidocs/" + bc.getClass().getName().replaceAll("\\.", "/") + ".html";
+  String bcName = bc == null ? null : bc.getClass().getSimpleName();
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>Attribute</th>
+    <th>Value</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>Implementation</td>
+    <td><a href="<%= bcUrl %>"><%= bcName %></a></td>
+    <td>Block cache implementing class</td>
+  </tr>
+</table>
+<p>See <a href="http://hbase.apache.org/book.html#block.cache">block cache</a> in the HBase Reference Guide for help.</p>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheConfig.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheConfig.jsp
new file mode 100644
index 00000000000..d73652de4cb
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheConfig.jsp
@@ -0,0 +1,72 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.hbase.io.hfile.CacheConfig" %>
+
+<%
+  CacheConfig cacheConfig = (CacheConfig) request.getAttribute("cacheConfig");
+if (cacheConfig == null) { %>
+  <p>CacheConfig is null</p>
+<% } else { %>
+<table class="table table-striped">
+  <tr>
+    <th>Attribute</th>
+    <th>Value</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>Cache DATA on Read</td>
+    <td><%= cacheConfig.shouldCacheDataOnRead() %></td>
+    <td>True if DATA blocks are cached on read
+      (INDEX & BLOOM blocks are always cached)</td>
+  </tr>
+  <tr>
+    <td>Cache DATA on Write</td>
+    <td><%= cacheConfig.shouldCacheDataOnWrite() %></td>
+    <td>True if DATA blocks are cached on write.</td>
+  </tr>
+  <tr>
+    <td>Cache INDEX on Write</td>
+    <td><%= cacheConfig.shouldCacheIndexesOnWrite() %></td>
+    <td>True if INDEX blocks are cached on write</td>
+  </tr>
+  <tr>
+    <td>Cache BLOOM on Write</td>
+    <td><%= cacheConfig.shouldCacheBloomsOnWrite() %></td>
+    <td>True if BLOOM blocks are cached on write</td>
+  </tr>
+  <tr>
+    <td>Evict blocks on Close</td>
+    <td><%= cacheConfig.shouldEvictOnClose() %></td>
+    <td>True if blocks are evicted from cache when an HFile
+      reader is closed</td>
+  </tr>
+  <tr>
+    <td>Cache DATA in compressed format</td>
+    <td><%= cacheConfig.shouldCacheDataCompressed() %></td>
+    <td>True if DATA blocks are cached in their compressed form</td>
+  </tr>
+  <tr>
+    <td>Prefetch on Open</td>
+    <td><%= cacheConfig.shouldPrefetchOnOpen() %></td>
+    <td>True if blocks are prefetched into cache on open</td>
+  </tr>
+</table>
+<% } %>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheEvictions.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheEvictions.jsp
new file mode 100644
index 00000000000..58870ca0072
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheEvictions.jsp
@@ -0,0 +1,45 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCache"
+         import="org.apache.hadoop.hbase.io.hfile.AgeSnapshot" %>
+
+<%
+  BlockCache bc = (BlockCache) request.getAttribute("bc");
+
+  AgeSnapshot ageAtEvictionSnapshot = bc.getStats().getAgeAtEvictionSnapshot();
+  // Only show if non-zero mean and stddev as is the case in combinedblockcache
+%>
+
+<tr>
+  <td>Evicted</td>
+  <td><%= String.format("%,d", bc.getStats().getEvictedCount()) %></td>
+  <td>The total number of blocks evicted</td>
+</tr>
+<tr>
+  <td>Evictions</td>
+  <td><%= String.format("%,d", bc.getStats().getEvictionCount()) %></td>
+  <td>The total number of times an eviction has occurred</td>
+</tr>
+<tr>
+  <td>Mean</td>
+  <td><%= String.format("%,d", (long)ageAtEvictionSnapshot.getMean()) %></td>
+  <td>Mean age of Blocks at eviction time (seconds)</td>
+</tr>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheHits.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheHits.jsp
new file mode 100644
index 00000000000..df9da58592c
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheHits.jsp
@@ -0,0 +1,92 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCache" %>
+
+<%
+  BlockCache bc = (BlockCache) request.getAttribute("bc");
+
+  int hitPeriods = 0;
+  for (int i = 0; i < bc.getStats().getNumPeriodsInWindow(); i++) {
+    if (bc.getStats().getWindowPeriods()[i] != null) {
+      hitPeriods++;
+    }
+  }
+%>
+
+<% if (hitPeriods > 0) { %>
+<script src="/static/js/blockCacheInit.js" type="text/javascript"></script>
+<% } %>
+<tr>
+  <td>Hits</td>
+  <td><%= String.format("%,d", bc.getStats().getHitCount()) %></td>
+  <td>Number requests that were cache hits</td>
+</tr>
+<tr>
+  <td>Hits Caching</td>
+  <td><%= String.format("%,d", bc.getStats().getHitCachingCount()) %></td>
+  <td>Cache hit block requests but only requests set to cache block if a miss</td>
+</tr>
+<tr>
+  <td>Misses</td>
+  <td><%= String.format("%,d", bc.getStats().getMissCount()) %></td>
+  <td>Block requests that were cache misses but set to cache missed blocks</td>
+</tr>
+<tr>
+  <td>Misses Caching</td>
+  <td><%= String.format("%,d", bc.getStats().getMissCachingCount()) %></td>
+  <td>Block requests that were cache misses but only requests set to use block cache</td>
+</tr>
+<tr>
+  <td>All Time Hit Ratio</td>
+  <td><%= String.format("%,.2f", bc.getStats().getHitRatio() * 100) %><%= "%" %></td>
+  <td>Hit Count divided by total requests count</td>
+</tr>
+<% for (int i = 0; i < hitPeriods; i++) { %>
+<%-- These rows are hidden on page load, blockCacheInit.js will display these as paginated. --%>
+<tr id="row-<%= i %>" class="item-row" style="display: none;">
+  <td>Hit Ratio for period starting at <%= bc.getStats().getWindowPeriods()[i] %></td>
+  <% if (bc.getStats().getRequestCounts()[i] > 0) { %>
+    <td><%= String.format("%,.2f", ((double)bc.getStats().getHitCounts()[i] / (double)bc.getStats().getRequestCounts()[i]) * 100.0) %><%= "%" %></td>
+  <% } else { %>
+    <td>No requests</td>
+  <% } %>
+    <td>Hit Count divided by total requests count over the <%= i %>th period of <%= bc.getStats().getPeriodTimeInMinutes() %> minutes</td>
+</tr>
+<% } %>
+<% if (hitPeriods > 0) { %>
+  <tr class="pagination-row">
+    <td colspan="3">
+      <div class="pagination-container">
+        <button id="prev-page" onclick="prevPage()">Previous</button>
+        <span id="page-buttons" class="page-numbers"></span>
+        <button id="next-page" onclick="nextPage()">Next</button>
+        <span id="page-info" class="page-info"></span>
+      </div>
+    </td>
+  </tr>
+<% } %>
+<% if (bc.getStats().getPeriodTimeInMinutes() > 0) { %>
+  <tr>
+    <td>Last <%= bc.getStats().getNumPeriodsInWindow()*bc.getStats().getPeriodTimeInMinutes() %> minutes Hit Ratio</td>
+    <td><%= String.format("%,.2f", bc.getStats().getHitRatioPastNPeriods() * 100.0) %><%= "%" %></td>
+    <td>Hit Count divided by total requests count for the last <%= bc.getStats().getNumPeriodsInWindow()*bc.getStats().getPeriodTimeInMinutes() %> minutes</td>
+  </tr>
+<% } %>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheLevel.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheLevel.jsp
new file mode 100644
index 00000000000..47cc6a5a985
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheLevel.jsp
@@ -0,0 +1,159 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.conf.Configuration"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCache"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCacheUtil"
+         import="org.apache.hadoop.hbase.io.hfile.LruBlockCache"
+         import="org.apache.hadoop.hbase.io.hfile.bucket.BucketCacheStats"
+         import="org.apache.hadoop.hbase.io.hfile.bucket.BucketCache"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix" %>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  Configuration configuration = regionServer.getConfiguration();
+
+  BlockCache bc = (BlockCache) request.getAttribute("bc");
+
+  String name = (String) request.getAttribute("name");
+%>
+
+<% if (bc == null) { %>
+  <p>No <%= name %> deployed</p>
+<% } else { %>
+
+<%
+  String bcUrl = "http://hbase.apache.org/devapidocs/" + bc.getClass().getName().replaceAll("\\.", "/") + ".html";
+  String bcName = bc.getClass().getSimpleName();
+  int maxCachedBlocksByFile = BlockCacheUtil.getMaxCachedBlocksByFile(configuration);
+
+  boolean isLru = bc instanceof LruBlockCache;
+
+  boolean isBucketCache = bc.getClass().getSimpleName().equals("BucketCache");
+  BucketCacheStats bucketCacheStats = null;
+  BucketCache bucketCache = null;
+  if (bc instanceof BucketCache) {
+    bucketCache = (BucketCache) bc;
+    bucketCacheStats = (BucketCacheStats) bc.getStats();
+  }
+%>
+
+<table id="blocks_summary" class="table table-striped">
+  <tr>
+    <th>Attribute</th>
+    <th>Value</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>Implementation</td>
+    <td><a href="<%= bcUrl %>"><%= bcName %></a></td>
+    <td>Class implementing this block cache Level</td>
+  </tr>
+<% if (isBucketCache) { %>
+  <tr>
+    <td>IOEngine</td>
+    <td><%= bucketCache.getIoEngine() %></td>
+    <td>Supported IOEngine types: offheap, file, files, mmap or pmem. See <a href="https://hbase.apache.org/book.html#hbase.bucketcache.ioengine">hbase.bucketcache.ioengine</a>.</td>
+  </tr>
+<% } %>
+  <tr>
+    <td>Cache Size Limit</td>
+    <td><%= TraditionalBinaryPrefix.long2String(bc.getMaxSize(), "B", 1) %></td>
+    <td>Max size of cache</td>
+  </tr>
+  <tr>
+    <td>Block Count</td>
+    <td><%= String.format("%,d", bc.getBlockCount()) %></td>
+    <td>Count of Blocks</td>
+  </tr>
+<% if (!isBucketCache) { %>
+  <tr>
+    <td>Data Block Count</td>
+    <td><%= String.format("%,d", bc.getDataBlockCount()) %></td>
+    <td>Count of DATA Blocks</td>
+  </tr>
+<% } %>
+<% if (isLru) { %>
+  <tr>
+    <td>Index Block Count</td>
+    <td><%= String.format("%,d", ((LruBlockCache)bc).getIndexBlockCount()) %></td>
+    <td>Count of INDEX Blocks</td>
+  </tr>
+  <tr>
+    <td>Bloom Block Count</td>
+    <td><%= String.format("%,d", ((LruBlockCache)bc).getBloomBlockCount()) %></td>
+    <td>Count of BLOOM Blocks</td>
+  </tr>
+<% } %>
+  <tr>
+    <td>Size of Blocks</td>
+    <td><%= TraditionalBinaryPrefix.long2String(bc.getCurrentSize(), "B", 1) %></td>
+    <td>Size of Blocks</td>
+  </tr>
+<% if (!isBucketCache) { %>
+  <tr>
+    <td>Size of Data Blocks</td>
+    <td><%= TraditionalBinaryPrefix.long2String(bc.getCurrentDataSize(), "B", 1) %></td>
+    <td>Size of DATA Blocks</td>
+  </tr>
+<% } %>
+<% if (isLru) { %>
+  <tr>
+    <td>Size of Index Blocks</td>
+    <td><%= TraditionalBinaryPrefix.long2String(((LruBlockCache)bc).getCurrentIndexSize(), "B", 1) %></td>
+    <td>Size of INDEX Blocks</td>
+  </tr>
+  <tr>
+    <td>Size of Bloom Blocks</td>
+    <td><%= TraditionalBinaryPrefix.long2String(((LruBlockCache)bc).getCurrentBloomSize(), "B", 1) %></td>
+    <td>Size of BLOOM Blocks</td>
+  </tr>
+<% } %>
+  <% request.setAttribute("bc", bc); %>
+  <jsp:include page="blockCacheEvictions.jsp"/>
+  <jsp:include page="blockCacheHits.jsp"/>
+
+<% if (isBucketCache) { %>
+  <tr>
+    <td>Hits per Second</td>
+    <td><%= bucketCacheStats.getIOHitsPerSecond() %></td>
+    <td>Block gets against this cache per second</td>
+  </tr>
+  <tr>
+    <td>Time per Hit</td>
+    <td><%= bucketCacheStats.getIOTimePerHit() %></td>
+    <td>Time per cache hit</td>
+  </tr>
+<% } %>
+</table>
+
+<%-- Call through to block cache Detail rendering template --%>
+<p>
+  View block cache <a href="?format=json&bcn=<%= name %>">as JSON</a> | Block cache <a href="?format=json&bcn=<%= name %>&bcv=file">as JSON by file</a>
+  <% if (bc.getBlockCount() > maxCachedBlocksByFile) { %>
+  <br>
+  <b>Note</b>: JSON view of block cache will be incomplete, because block count <%= bc.getBlockCount() %> is greater than <i>hbase.ui.blockcache.by.file.max</i> value of <%= maxCachedBlocksByFile %>.
+  Increase that value to get a complete picture.
+  <% } %>
+</p>
+<% } %>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheStats.jsp
new file mode 100644
index 00000000000..c30160b7621
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheStats.jsp
@@ -0,0 +1,58 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCache"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix" %>
+
+<%
+  BlockCache bc = (BlockCache) request.getAttribute("bc");
+if (bc == null) { %>
+  <p>BlockCache is null</p>
+<% } else { %>
+<table class="table table-striped">
+  <tr>
+    <th>Attribute</th>
+    <th>Value</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>Size</td>
+    <td><%= TraditionalBinaryPrefix.long2String(bc.getCurrentSize(),
+      "B", 1) %></td>
+    <td>Current size of block cache in use</td>
+  </tr>
+  <tr>
+    <td>Free</td>
+    <td><%= TraditionalBinaryPrefix.long2String(bc.getFreeSize(),
+      "B", 1) %></td>
+    <td>The total free memory currently available to store more cache entries</td>
+  </tr>
+  <tr>
+    <td>Count</td>
+    <td><%= String.format("%,d", bc.getBlockCount()) %></td>
+    <td>Number of blocks in block cache</td>
+  </tr>
+  <% request.setAttribute("bc", bc); %>
+  <jsp:include page="blockCacheEvictions.jsp"/>
+  <jsp:include page="blockCacheHits.jsp"/>
+</table>
+<p>If block cache is made up of more than one cache -- i.e. a L1 and a L2 -- then the above
+  are combined counts. Request count is sum of hits and misses.</p>
+<% } %>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheView.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheView.jsp
new file mode 100644
index 00000000000..be6061bc652
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/blockCacheView.jsp
@@ -0,0 +1,62 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCache"
+         import="org.apache.hadoop.conf.Configuration"
+         import="org.apache.hadoop.hbase.io.hfile.BlockCacheUtil"
+         import="org.apache.hadoop.hbase.io.hfile.CachedBlock"
+         import="org.apache.hadoop.hbase.regionserver.http.RSStatusConstants" %><%
+  // This template is used to give views on an individual block cache as JSON.
+
+  Configuration conf = (Configuration) request.getAttribute("conf");
+  String bcn = (String) request.getAttribute(RSStatusConstants.BLOCK_CACHE_NAME);
+  String bcv = (String) request.getAttribute(RSStatusConstants.BLOCK_CACHE_V);
+  BlockCache bc = (BlockCache) request.getAttribute("blockCache");
+
+  BlockCache [] bcs = bc == null ? null : bc.getBlockCaches();
+  if (bcn.equals(RSStatusConstants.BLOCK_CACHE_NAME_L1)) {
+    bc = bcs == null || bcs.length == 0? bc: bcs[0];
+  } else {
+    if (bcs == null || bcs.length < 2) {
+      System.out.println("There is no L2 block cache");
+      return;
+    }
+    bc = bcs[1];
+  }
+  if (bc == null) {
+    System.out.println("There is no block cache");
+    return;
+  }
+  BlockCacheUtil.CachedBlocksByFile cbsbf = BlockCacheUtil.getLoadedCachedBlocksByFile(conf, bc);
+  if (bcv.equals(RSStatusConstants.BLOCK_CACHE_V_FILE)) {
+    boolean firstEntry = true; %>
+    [<% for (Map.Entry<String, NavigableSet<CachedBlock>> e: cbsbf.getCachedBlockStatsByFile().entrySet()) { %>
+    <% if (!firstEntry) { %>,<% } %><%= BlockCacheUtil.toJSON(e.getKey(), e.getValue()) %>
+    <%
+      if (firstEntry) {
+        firstEntry = false;
+      }
+    %>
+    <% } %>]
+
+<% } else { %>
+[ <%= BlockCacheUtil.toJSON(bc) %>, <%= BlockCacheUtil.toJSON(cbsbf) %> ]
+<% } %>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/footer.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/footer.jsp
index 02a36e9c23f..4b2d6cb2f5e 100644
--- a/hbase-server/src/main/resources/hbase-webapps/regionserver/footer.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/footer.jsp
@@ -17,14 +17,6 @@
 * limitations under the License.
 */
 --%>
-    <script src="/static/js/jquery.min.js" type="text/javascript"></script>
-    <script src="/static/js/bootstrap.bundle.min.js" type="text/javascript"></script>
-    <script src="/static/js/tab.js" type="text/javascript"></script>
-    <script type="text/javascript">
-      $(document).ready(function() {
-        $('nav.navbar li.active').removeClass('active');
-        $('a[href="' + location.pathname + '"]').addClass('active');
-      });
-    </script>
+   <jsp:include page="scripts.jsp"/>
   </body>
 </html>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/header.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/header.jsp
index 14d22363c51..47e46017b74 100644
--- a/hbase-server/src/main/resources/hbase-webapps/regionserver/header.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/header.jsp
@@ -37,7 +37,7 @@
   <body>
   <nav class="navbar navbar-expand-md navbar-light fixed-top bg-light">
     <div class="container-fluid">
-      <a class="navbar-brand" href="/rs-status">
+      <a class="navbar-brand" href="/regionserver.jsp">
         <img src="/static/hbase_logo_small.png" alt="HBase Logo"/>
       </a>
       <button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
@@ -45,7 +45,7 @@
       </button>
       <div class="collapse navbar-collapse">
         <ul class="navbar-nav">
-          <li class="nav-item"><a class="nav-link" href="/rs-status">Home</a></li>
+          <li class="nav-item"><a class="nav-link" href="/regionserver.jsp">Home</a></li>
           <li class="nav-item"><a class="nav-link" href="/processRS.jsp">Process Metrics</a></li>
           <li class="nav-item"><a class="nav-link" href="/logs/">Local Logs</a></li>
           <li class="nav-item"><a class="nav-link" href="/rsOperationDetails.jsp">Operation Details</a></li>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/index.html b/hbase-server/src/main/resources/hbase-webapps/regionserver/index.html
index 47fc1b9b30b..ce57b77f1fb 100644
--- a/hbase-server/src/main/resources/hbase-webapps/regionserver/index.html
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/index.html
@@ -17,4 +17,4 @@
  * limitations under the License.
  */
 -->
-<meta HTTP-EQUIV="REFRESH" content="0;url=/rs-status"/>
+<meta HTTP-EQUIV="REFRESH" content="0;url=/regionserver.jsp"/>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionList.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionList.jsp
new file mode 100644
index 00000000000..4dc03adb1d0
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionList.jsp
@@ -0,0 +1,75 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.client.RegionInfo" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  List<RegionInfo> onlineRegions = (List<RegionInfo>) request.getAttribute("onlineRegions");
+
+  if (onlineRegions != null && onlineRegions.size() > 0) {
+
+    Collections.sort(onlineRegions, RegionInfo.COMPARATOR);
+%>
+<div class="tabbable">
+  <ul class="nav nav-pills" role="tablist">
+    <li class="nav-item"><a class="nav-link active" href="#tab_regionBaseInfo" data-bs-toggle="tab" role="tab">Base Info</a> </li>
+    <li class="nav-item"><a class="nav-link" href="#tab_regionRequestStats" data-bs-toggle="tab" role="tab">Request metrics</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_regionStoreStats" data-bs-toggle="tab" role="tab">Storefile Metrics</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_regionMemstoreStats" data-bs-toggle="tab" role="tab">Memstore Metrics</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_regionCompactStats" data-bs-toggle="tab" role="tab">Compaction Metrics</a></li>
+  </ul>
+  <div class="tab-content">
+    <div class="tab-pane active" id="tab_regionBaseInfo" role="tabpanel">
+      <jsp:include page="regionListBaseInfo.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_regionRequestStats" role="tabpanel">
+      <jsp:include page="regionListRequestStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_regionStoreStats" role="tabpanel">
+      <jsp:include page="regionListStoreStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_regionMemstoreStats" role="tabpanel">
+      <jsp:include page="regionListMemstoreStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_regionCompactStats" role="tabpanel">
+      <jsp:include page="regionListCompactStats.jsp"/>
+    </div>
+  </div>
+</div>
+<p>Region names are made of the containing table's name, a comma,
+  the start key, a comma, and a randomly generated region id.  To illustrate,
+  the region named
+  <em>domains,apache.org,5464829424211263407</em> is party to the table
+  <em>domains</em>, has an id of <em>5464829424211263407</em> and the first key
+  in the region is <em>apache.org</em>.  The <em>hbase:meta</em> 'table' is an internal
+  system table (or a 'catalog' table in db-speak).
+  The hbase:meta table keeps a list of all regions in the system. The empty key is used to denote
+  table start and table end.  A region with an empty start key is the first region in a table.
+  If a region has both an empty start key and an empty end key, it's the only region in the
+  table. See <a href="http://hbase.apache.org">HBase Home</a> for further explication.<p>
+<% } else { %>
+  <p>Not serving regions</p>
+<% } %>
+
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListBaseInfo.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListBaseInfo.jsp
new file mode 100644
index 00000000000..a70819d8e9a
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListBaseInfo.jsp
@@ -0,0 +1,58 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.client.RegionInfo"
+         import="org.apache.hadoop.hbase.client.RegionInfoDisplay"
+         import="org.apache.hadoop.hbase.util.Bytes" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  List<RegionInfo> onlineRegions = (List<RegionInfo>) request.getAttribute("onlineRegions");
+%>
+<table id="baseStatsTable" class="tablesorter table table-striped">
+  <thead>
+  <tr>
+    <th>Region Name</th>
+    <th class="cls_emptyMin">Start Key</th>
+    <th class="cls_emptyMax">End Key</th>
+    <th>ReplicaID</th>
+  </tr>
+  </thead>
+
+  <tbody>
+  <% for (RegionInfo r: onlineRegions) { %>
+  <tr>
+    <%
+      String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
+        regionServer.getConfiguration());
+      %>
+    <td><a href="region.jsp?name=<%= r.getEncodedName() %>"><%= displayName %></a></td>
+    <td><%= Bytes.toStringBinary(RegionInfoDisplay.getStartKeyForDisplay(r,
+      regionServer.getConfiguration())) %></td>
+    <td><%= Bytes.toStringBinary(RegionInfoDisplay.getEndKeyForDisplay(r,
+      regionServer.getConfiguration())) %></td>
+    <td><%= r.getReplicaId() %></td>
+  </tr>
+  <% } %>
+  </tbody>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListCompactStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListCompactStats.jsp
new file mode 100644
index 00000000000..07cf41e8334
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListCompactStats.jsp
@@ -0,0 +1,79 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.client.RegionInfo"
+         import="org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.RegionLoad"
+         import="org.apache.commons.lang3.time.FastDateFormat"
+         import="org.apache.hadoop.hbase.client.RegionInfoDisplay" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  List<RegionInfo> onlineRegions = (List<RegionInfo>) request.getAttribute("onlineRegions");
+
+%>
+<table id="compactionStatsTable" class="tablesorter table table-striped">
+  <thead>
+  <tr>
+    <th>Region Name</th>
+    <th class="cls_separator">Num. Compacting Cells</th>
+    <th class="cls_separator">Num. Compacted Cells</th>
+    <th>Compaction Progress</th>
+    <th data-date-format="yyyymmdd hhmm zz">Last Major Compaction</th>
+  </tr>
+  </thead>
+
+  <tbody>
+    <% for (RegionInfo r: onlineRegions) { %>
+      <tr>
+
+        <%
+          RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
+          String percentDone = "";
+          String compactTime = "";
+          if  (load != null) {
+            if (load.getTotalCompactingKVs() > 0) {
+              percentDone = String.format("%.2f", 100 *
+                ((float) load.getCurrentCompactedKVs() / load.getTotalCompactingKVs())) + "%";
+            }
+            if (load.getLastMajorCompactionTs() > 0) {
+              FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm (ZZ)");
+              compactTime = fdf.format(load.getLastMajorCompactionTs());
+            }
+          }
+          String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
+            regionServer.getConfiguration());
+        %>
+        <td><a href="region.jsp?name=<%= r.getEncodedName() %>"><%= displayName %></a></td>
+        <% if (load != null) { %>
+          <td><%= String.format("%,1d", load.getTotalCompactingKVs()) %></td>
+          <td><%= String.format("%,1d", load.getCurrentCompactedKVs()) %></td>
+          <td><%= percentDone %></td>
+          <td><%= compactTime %></td>
+        <% } %>
+
+      </tr>
+
+    <% } %>
+  </tbody>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListMemstoreStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListMemstoreStats.jsp
new file mode 100644
index 00000000000..b81e42ce6dd
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListMemstoreStats.jsp
@@ -0,0 +1,70 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.client.RegionInfo"
+         import="org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos"
+         import="org.apache.hadoop.hbase.client.RegionInfoDisplay" %>
+<%@ page import="org.apache.hadoop.util.StringUtils" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  List<RegionInfo> onlineRegions = (List<RegionInfo>) request.getAttribute("onlineRegions");
+
+%>
+<table id="memstoreStatsTable" class="tablesorter table table-striped">
+  <thead>
+  <tr>
+    <th>Region Name</th>
+    <th class="cls_filesize">Memstore Size</th>
+  </tr>
+  </thead>
+
+  <tbody>
+    <% for (RegionInfo r: onlineRegions) { %>
+      <tr>
+
+        <%
+          final String ZEROMB = "0 MB";
+          String memStoreSizeMBStr = ZEROMB;
+          ClusterStatusProtos.RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
+          String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
+            regionServer.getConfiguration());
+          if (load != null) {
+            long memStoreSizeMB = load.getMemStoreSizeMB();
+            if (memStoreSizeMB > 0) {
+              memStoreSizeMBStr = StringUtils.TraditionalBinaryPrefix.long2String(
+                memStoreSizeMB * StringUtils.TraditionalBinaryPrefix.MEGA.value, "B", 1);
+            }
+          }
+        %>
+
+        <td><a href="region.jsp?name=<%= r.getEncodedName() %>"><%= displayName %></a></td>
+        <% if (load != null) { %>
+        <td><%= memStoreSizeMBStr %></td>
+        <% } %>
+
+      </tr>
+    <% } %>
+  </tbody>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListRequestStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListRequestStats.jsp
new file mode 100644
index 00000000000..2d0ef5cd1d2
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListRequestStats.jsp
@@ -0,0 +1,61 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.client.RegionInfo"
+         import="org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos"
+         import="org.apache.hadoop.hbase.client.RegionInfoDisplay" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  List<RegionInfo> onlineRegions = (List<RegionInfo>) request.getAttribute("onlineRegions");
+%>
+<table id="requestStatsTable" class="tablesorter table table-striped">
+  <thead>
+  <tr>
+    <th>Region Name</th>
+    <th class="cls_separator">Read Request Count</th>
+    <th class="cls_separator">Filtered Read Request Count</th>
+    <th class="cls_separator">Write Request Count</th>
+  </tr>
+  </thead>
+
+  <tbody>
+  <% for (RegionInfo r: onlineRegions) { %>
+
+  <tr>
+    <%
+      ClusterStatusProtos.RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
+      String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
+      regionServer.getConfiguration());
+    %>
+    <td><a href="region.jsp?name=<%= r.getEncodedName() %>"><%= displayName %></a></td>
+    <% if (load != null) { %>
+      <td><%= String.format("%,1d", load.getReadRequestsCount()) %></td>
+      <td><%= String.format("%,1d", load.getFilteredReadRequestsCount()) %></td>
+      <td><%= String.format("%,1d", load.getWriteRequestsCount()) %></td>
+    <% } %>
+  </tr>
+  <% } %>
+  </tbody>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListStoreStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListStoreStats.jsp
new file mode 100644
index 00000000000..b663b74536e
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionListStoreStats.jsp
@@ -0,0 +1,119 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.client.RegionInfo"
+         import="org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos"
+         import="org.apache.hadoop.hbase.client.RegionInfoDisplay"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix"
+         import="org.apache.hadoop.hbase.regionserver.*"
+         import="org.apache.hadoop.util.StringUtils" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  List<RegionInfo> onlineRegions = (List<RegionInfo>) request.getAttribute("onlineRegions");
+%>
+<table id="storeStatsTable" class="tablesorter table table-striped">
+  <thead>
+  <tr>
+    <th>Region Name</th>
+    <th class="cls_separator">Num. Stores</th>
+    <th class="cls_separator">Num. Storefiles</th>
+    <th class="cls_filesize">Storefile Size Uncompressed</th>
+    <th class="cls_filesize">Storefile Size</th>
+    <th class="cls_filesize">Index Size</th>
+    <th class="cls_filesize">Bloom Size</th>
+    <th>Data Locality</th>
+    <th>Len Of Biggest Cell</th>
+    <th>% Cached</th>
+  </tr>
+  </thead>
+
+  <tbody>
+
+  <% for (RegionInfo r: onlineRegions) { %>
+    <tr>
+      <%
+        final String ZEROMB = "0 MB";
+        final String ZEROKB = "0 KB";
+        String uncompressedStorefileSizeStr = ZEROMB;
+        String storefileSizeStr = ZEROMB;
+        String indexSizeStr = ZEROKB;
+        String bloomSizeStr = ZEROKB;
+        ClusterStatusProtos.RegionLoad load = regionServer.createRegionLoad(r.getEncodedName());
+        String displayName = RegionInfoDisplay.getRegionNameAsStringForDisplay(r,
+          regionServer.getConfiguration());
+        if (load != null) {
+          long uncompressedStorefileSize  = load.getStoreUncompressedSizeMB();
+          long storefileSize  = load.getStorefileSizeMB();
+          long indexSize  = load.getTotalStaticIndexSizeKB();
+          long bloomSize  = load.getTotalStaticBloomSizeKB();
+          if (uncompressedStorefileSize > 0) {
+            uncompressedStorefileSizeStr = TraditionalBinaryPrefix.long2String(
+              uncompressedStorefileSize * TraditionalBinaryPrefix.MEGA.value, "B", 1);
+          }
+          if (storefileSize > 0) {
+            storefileSizeStr = TraditionalBinaryPrefix.long2String(storefileSize
+              * TraditionalBinaryPrefix.MEGA.value, "B", 1);
+          }
+          if(indexSize > 0) {
+            indexSizeStr = TraditionalBinaryPrefix.long2String(indexSize
+              * TraditionalBinaryPrefix.KILO.value, "B", 1);
+          }
+          if (bloomSize > 0) {
+            bloomSizeStr = TraditionalBinaryPrefix.long2String(bloomSize
+              * TraditionalBinaryPrefix.KILO.value, "B", 1);
+          }
+        }
+        long lenOfBiggestCellInRegion = -1L;
+        Region region = regionServer.getRegion(r.getEncodedName());
+        if (region != null) {
+          List<? extends Store> stores = region.getStores();
+          for (Store store : stores) {
+            Collection<? extends StoreFile> storeFiles = store.getStorefiles();
+            for (StoreFile sf : storeFiles) {
+              long lenOfBiggestCell = ((HStoreFile)sf).getFileInfo().getHFileInfo().getLenOfBiggestCell();
+              if (lenOfBiggestCellInRegion < lenOfBiggestCell) {
+                lenOfBiggestCellInRegion = lenOfBiggestCell;
+              }
+            }
+          }
+        }
+      %>
+
+      <td><a href="region.jsp?name=<%= r.getEncodedName() %>"><%= displayName %></a></td>
+      <% if (load != null) { %>
+        <td><%= String.format("%,1d", load.getStores()) %></td>
+        <td><%= String.format("%,1d", load.getStorefiles()) %></td>
+        <td><%= uncompressedStorefileSizeStr %></td>
+        <td><%= storefileSizeStr %></td>
+        <td><%= indexSizeStr %></td>
+        <td><%= bloomSizeStr %></td>
+        <td><%= load.getDataLocality() %></td>
+        <td><%= String.format("%,1d", lenOfBiggestCellInRegion) %></td>
+        <td><%= StringUtils.formatPercent(load.getCurrentRegionCachedRatio(), 2) %></td>
+      <% } %>
+    </tr>
+  <% } %>
+
+  </tbody>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionserver.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionserver.jsp
index ee63075d5d6..f25377e7e4d 100644
--- a/hbase-server/src/main/resources/hbase-webapps/regionserver/regionserver.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/regionserver.jsp
@@ -17,4 +17,126 @@
  * limitations under the License.
  */
 --%>
-<meta HTTP-EQUIV="REFRESH" content="0;url=/rs-status"/>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.ServerName"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.client.RegionInfo"
+         import="org.apache.hadoop.hbase.ServerName"
+         import="org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil"
+         import="org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ServerInfo"
+         import="org.apache.hadoop.hbase.zookeeper.MasterAddressTracker"
+         import="org.apache.hadoop.hbase.regionserver.http.RSStatusConstants" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  String format = request.getParameter(RSStatusConstants.FORMAT);
+  if (format == null) {
+    format = RSStatusConstants.FORMAT_HTML;
+  }
+  if (RSStatusConstants.FORMAT_JSON.equals(format)) {
+    response.setContentType("application/json");
+  } else {
+    response.setContentType("text/html");
+  }
+
+  if (!regionServer.isOnline()) {
+    response.getWriter().write("The RegionServer is initializing!");
+    response.getWriter().close();
+    return;
+  }
+
+  String filter = request.getParameter(RSStatusConstants.FILTER);
+  if (filter == null) {
+    filter = RSStatusConstants.FILTER_GENERAL;
+  }
+  String bcn = request.getParameter(RSStatusConstants.BLOCK_CACHE_NAME);
+  String bcv = request.getParameter(RSStatusConstants.BLOCK_CACHE_V);
+  if (bcv == null) {
+    bcv = "";
+  }
+  // If json AND bcn is NOT an empty string presume it a block cache view request.
+  if (format.equals(RSStatusConstants.FORMAT_JSON) && bcn != null && bcn.length() > 0) {
+    request.setAttribute("conf", regionServer.getConfiguration());
+    request.setAttribute(RSStatusConstants.BLOCK_CACHE_NAME, bcn);
+    request.setAttribute(RSStatusConstants.BLOCK_CACHE_V, bcv);
+    request.setAttribute("blockCache", regionServer.getBlockCache().orElse(null));
+%><jsp:include page="blockCacheView.jsp"/>
+<%
+    return;
+  }
+  else if (format.equals(RSStatusConstants.FORMAT_JSON)) {
+    request.setAttribute(RSStatusConstants.FILTER, filter);
+    request.setAttribute(RSStatusConstants.FORMAT, RSStatusConstants.FORMAT_JSON); %>
+  <jsp:include page="taskMonitor.jsp"/>
+<%
+    return;
+  }
+
+  ServerInfo serverInfo = ProtobufUtil.getServerInfo(null, regionServer.getRSRpcServices());
+  ServerName serverName = ProtobufUtil.toServerName(serverInfo.getServerName());
+  List<RegionInfo> onlineRegions = ProtobufUtil.getOnlineRegions(regionServer.getRSRpcServices());
+  MasterAddressTracker masterAddressTracker = regionServer.getMasterAddressTracker();
+  ServerName masterServerName = masterAddressTracker == null ? null
+    : masterAddressTracker.getMasterAddress();
+  int infoPort = masterAddressTracker == null ? 0 : masterAddressTracker.getMasterInfoPort();
+
+  String title = "HBase Region Server: " + serverName.getHostname();
+  pageContext.setAttribute("pageTitle", title);
+%>
+
+<jsp:include page="header.jsp">
+  <jsp:param name="pageTitle" value="${pageTitle}"/>
+</jsp:include>
+
+<div class="container-fluid content">
+  <div class="row inner_header">
+    <div class="page-header">
+      <h1>RegionServer <small><%= serverName %></small></h1>
+    </div>
+  </div>
+  <div class="row">
+
+    <section>
+      <h2>Server Metrics</h2>
+      <jsp:include page="serverMetrics.jsp"/>
+    </section>
+
+    <section>
+      <h2>Block Cache</h2>
+      <jsp:include page="blockCache.jsp"/>
+    </section>
+
+    <section>
+      <% request.setAttribute(RSStatusConstants.PARENT, "/regionserver.jsp"); %>
+      <jsp:include page="taskMonitor.jsp"/>
+    </section>
+
+    <section>
+      <h2>Regions</h2>
+      <% request.setAttribute("onlineRegions", onlineRegions); %>
+      <jsp:include page="regionList.jsp"/>
+    </section>
+
+    <section>
+      <h2>Replication Status</h2>
+      <jsp:include page="replicationStatus.jsp"/>
+    </section>
+
+    <section>
+      <h2>Software Attributes</h2>
+      <% request.setAttribute("masterServerName", masterServerName); %>
+      <% request.setAttribute("infoPort", infoPort); %>
+      <jsp:include page="softwareAttributes.jsp"/>
+    </section>
+
+  </div> <!--/.row -->
+</div> <!-- /.container-fluid content -->
+
+  <jsp:include page="scripts.jsp" />
+  <script src="/static/js/jquery.tablesorter.min.js" type="text/javascript"></script>
+  <script src ="/static/js/regionServerStatusInit.js" type="text/javascript"></script>
+</body>
+</html>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatus.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatus.jsp
new file mode 100644
index 00000000000..9825669b0a1
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatus.jsp
@@ -0,0 +1,53 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import ="org.apache.hadoop.hbase.replication.regionserver.ReplicationStatus" %>
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+  Map<String, ReplicationStatus> walGroupsReplicationStatus = regionServer.getWalGroupsReplicationStatus();
+
+  if (walGroupsReplicationStatus != null && walGroupsReplicationStatus.size() > 0) {
+%>
+
+<div class="tabbable">
+  <ul class="nav nav-pills" role="tablist">
+    <li class="nav-item"><a class="nav-link active" href="#tab_currentLog" data-bs-toggle="tab" role="tab">Current Log</a> </li>
+    <li class="nav-item"><a class="nav-link" href="#tab_replicationDelay" data-bs-toggle="tab" role="tab">Replication Delay</a></li>
+  </ul>
+  <div class="tab-content">
+    <% request.setAttribute("metrics", walGroupsReplicationStatus); %>
+    <div class="tab-pane active" id="tab_currentLog" role="tabpanel">
+      <jsp:include page="replicationStatusCurrentLog.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_replicationDelay" role="tabpanel">
+      <jsp:include page="replicationStatusReplicationDelay.jsp"/>
+    </div>
+  </div>
+</div>
+<p> If the replication delay is UNKNOWN, that means this walGroup doesn't start replicate yet and it may get disabled.
+  If the size of log is 0, it means we are replicating current HLog, thus we can't get accurate size since it's not closed yet.</p>
+
+<% } else { %>
+  <p>No Replication Metrics for Peers</p>
+<% } %>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatusCurrentLog.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatusCurrentLog.jsp
new file mode 100644
index 00000000000..254620139de
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatusCurrentLog.jsp
@@ -0,0 +1,47 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.procedure2.util.StringUtils"
+         import ="org.apache.hadoop.hbase.replication.regionserver.ReplicationStatus" %>
+<%
+  Map<String, ReplicationStatus> metrics = (Map<String, ReplicationStatus>) request.getAttribute("metrics");
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>PeerId</th>
+    <th>WalGroup</th>
+    <th>Current Log</th>
+    <th>Size</th>
+    <th>Queue Size</th>
+    <th>Offset</th>
+  </tr>
+  <% for (Map.Entry<String, ReplicationStatus> entry: metrics.entrySet()) { %>
+    <tr>
+      <td><%= entry.getValue().getPeerId() %></td>
+      <td><%= entry.getValue().getWalGroup() %></td>
+      <td><%= entry.getValue().getCurrentPath() %> </td>
+      <td><%= StringUtils.humanSize(entry.getValue().getFileSize()) %></td>
+      <td><%= entry.getValue().getQueueSize() %></td>
+      <td><%= StringUtils.humanSize(entry.getValue().getCurrentPosition()) %></td>
+    </tr>
+  <% } %>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatusReplicationDelay.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatusReplicationDelay.jsp
new file mode 100644
index 00000000000..19ea1f28b5d
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/replicationStatusReplicationDelay.jsp
@@ -0,0 +1,44 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.procedure2.util.StringUtils"
+         import ="org.apache.hadoop.hbase.replication.regionserver.ReplicationStatus" %>
+<%
+  Map<String, ReplicationStatus> metrics = (Map<String, ReplicationStatus>) request.getAttribute("metrics");
+%>
+<table class="table table-striped">
+  <tr>
+    <th>PeerId</th>
+    <th>WalGroup</th>
+    <th>Current Log</th>
+    <th>Last Shipped Age</th>
+    <th>Replication Delay</th>
+  </tr>
+  <%  for (Map.Entry<String, ReplicationStatus> entry: metrics.entrySet()) { %>
+    <tr>
+      <td><%= entry.getValue().getPeerId() %></td>
+      <td><%= entry.getValue().getWalGroup() %></td>
+      <td><%= entry.getValue().getCurrentPath() %> </td>
+      <td><%= StringUtils.humanTimeDiff(entry.getValue().getAgeOfLastShippedOp()) %></td>
+      <td><%= entry.getValue().getReplicationDelay() == Long.MAX_VALUE ? "UNKNOWN" : StringUtils.humanTimeDiff(entry.getValue().getReplicationDelay()) %></td>
+    </tr>
+  <% } %>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/footer.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/scripts.jsp
similarity index 98%
copy from hbase-server/src/main/resources/hbase-webapps/regionserver/footer.jsp
copy to hbase-server/src/main/resources/hbase-webapps/regionserver/scripts.jsp
index 02a36e9c23f..d987621a119 100644
--- a/hbase-server/src/main/resources/hbase-webapps/regionserver/footer.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/scripts.jsp
@@ -26,5 +26,3 @@
         $('a[href="' + location.pathname + '"]').addClass('active');
       });
     </script>
-  </body>
-</html>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetrics.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetrics.jsp
new file mode 100644
index 00000000000..08af35feb66
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetrics.jsp
@@ -0,0 +1,55 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8" %>
+
+<div class="tabbable">
+  <ul class="nav nav-pills" role="tablist">
+    <li class="nav-item"><a class="nav-link active" href="#tab_baseStats" data-bs-toggle="tab" role="tab">Base Stats</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_memoryStats" data-bs-toggle="tab" role="tab">Memory</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_requestStats" data-bs-toggle="tab" role="tab">Requests</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_walStats" data-bs-toggle="tab" role="tab">WALs</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_storeStats" data-bs-toggle="tab" role="tab">Storefiles</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_queueStats" data-bs-toggle="tab" role="tab">Queues</a></li>
+    <li class="nav-item"><a class="nav-link" href="#tab_byteBuffAllocatorStats" data-bs-toggle="tab" role="tab">ByteBuffAllocator Stats</a></li>
+  </ul>
+  <div class="tab-content">
+    <div class="tab-pane active" id="tab_baseStats" role="tabpanel">
+      <jsp:include page="serverMetricsBaseStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_memoryStats" role="tabpanel">
+      <jsp:include page="serverMetricsMemoryStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_requestStats" role="tabpanel">
+      <jsp:include page="serverMetricsRequestStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_walStats" role="tabpanel">
+      <jsp:include page="serverMetricsWalStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_storeStats" role="tabpanel">
+      <jsp:include page="serverMetricsStoreStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_queueStats" role="tabpanel">
+      <jsp:include page="serverMetricsQueueStats.jsp"/>
+    </div>
+    <div class="tab-pane" id="tab_byteBuffAllocatorStats" role="tabpanel">
+      <jsp:include page="serverMetricsByteBuffAllocatorStats.jsp"/>
+    </div>
+  </div>
+</div>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsBaseStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsBaseStats.jsp
new file mode 100644
index 00000000000..59c2c46fe5c
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsBaseStats.jsp
@@ -0,0 +1,49 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapper" %>
+
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  MetricsRegionServerWrapper mWrap = regionServer.getMetrics().getRegionServerWrapper();
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>Requests Per Second</th>
+    <th>Num. Regions</th>
+    <th>Block locality</th>
+    <th>Block locality (Secondary replicas)</th>
+    <th>Slow WAL Append Count</th>
+  </tr>
+  <tr>
+    <td><%= String.format("%.0f", mWrap.getRequestsPerSecond()) %></td>
+    <td><%= mWrap.getNumOnlineRegions() %></td>
+    <td><%= String.format("%.3f",mWrap.getPercentFileLocal()) %><%= "%" %></td>
+    <td><%= String.format("%.3f",mWrap.getPercentFileLocalSecondaryRegions()) %><%= "%" %></td>
+    <td><%= mWrap.getNumWALSlowAppend() %></td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsByteBuffAllocatorStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsByteBuffAllocatorStats.jsp
new file mode 100644
index 00000000000..65334a20c23
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsByteBuffAllocatorStats.jsp
@@ -0,0 +1,51 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix"
+         import="org.apache.hadoop.hbase.io.ByteBuffAllocator" %>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  ByteBuffAllocator bbAllocator = regionServer.getRpcServer().getByteBuffAllocator();
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>Total Heap Allocation</th>
+    <th>Total Pool Allocation</th>
+    <th>Heap Allocation Ratio</th>
+    <th>Total Buffer Count</th>
+    <th>Used Buffer Count</th>
+    <th>Buffer Size</th>
+  </tr>
+  <tr>
+    <td><%= TraditionalBinaryPrefix.long2String(ByteBuffAllocator.getHeapAllocationBytes(bbAllocator, ByteBuffAllocator.HEAP), "B", 1) %></td>
+    <td><%= TraditionalBinaryPrefix.long2String(bbAllocator.getPoolAllocationBytes(), "B", 1) %></td>
+    <td><%= String.format("%.3f", ByteBuffAllocator.getHeapAllocationRatio(bbAllocator, ByteBuffAllocator.HEAP) * 100) %><%= "%" %></td>
+    <td><%= bbAllocator.getTotalBufferCount() %></td>
+    <td><%= bbAllocator.getUsedBufferCount() %></td>
+    <td><%= TraditionalBinaryPrefix.long2String(bbAllocator.getBufferSize(), "B", 1) %></td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsMemoryStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsMemoryStats.jsp
new file mode 100644
index 00000000000..b3a0443add6
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsMemoryStats.jsp
@@ -0,0 +1,78 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapper"
+         import="java.lang.management.MemoryUsage"
+         import="org.apache.hadoop.hbase.io.util.MemorySizeUtil"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix" %>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  MetricsRegionServerWrapper mWrap = regionServer.getMetrics().getRegionServerWrapper();
+
+  long usedHeap = -1L;
+  long maxHeap = -1L;
+  final MemoryUsage usage = MemorySizeUtil.safeGetHeapMemoryUsage();
+  if (usage != null) {
+    maxHeap = usage.getMax();
+    usedHeap = usage.getUsed();
+  }
+%>
+<table class="table table-striped">
+  <tr>
+    <th>Used Heap</th>
+    <th>Max Heap</th>
+    <th>Direct Memory Used</th>
+    <th>Direct Memory Configured</th>
+    <th>Memstore On-Heap Size / Limit</th>
+    <th>Memstore Off-Heap Size / Limit</th>
+    <th>Memstore Data Size (On&&Off Heap)</th>
+  </tr>
+  <tr>
+    <td>
+      <%= TraditionalBinaryPrefix.long2String(usedHeap, "B", 1) %>
+    </td>
+    <td>
+      <%= TraditionalBinaryPrefix.long2String(maxHeap, "B", 1) %>
+    </td>
+    <td>
+      <%= TraditionalBinaryPrefix.long2String(DirectMemoryUtils.getDirectMemoryUsage(), "B", 1) %>
+    </td>
+    <td>
+      <%= TraditionalBinaryPrefix.long2String(DirectMemoryUtils.getDirectMemorySize(), "B", 1) %>
+    </td>
+    <td>
+      <%= TraditionalBinaryPrefix.long2String(mWrap.getOnHeapMemStoreSize(), "B", 1) + " / "
+        + TraditionalBinaryPrefix.long2String(mWrap.getOnHeapMemStoreLimit(), "B", 1) %>
+    </td>
+    <td>
+      <%= TraditionalBinaryPrefix.long2String(mWrap.getOffHeapMemStoreSize(), "B", 1) + " / "
+        + TraditionalBinaryPrefix.long2String(mWrap.getOffHeapMemStoreLimit(), "B", 1) %>
+    </td>
+    <td>
+      <%= TraditionalBinaryPrefix.long2String(mWrap.getMemStoreSize(), "B", 1) %>
+    </td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsQueueStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsQueueStats.jsp
new file mode 100644
index 00000000000..3a6bc509fde
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsQueueStats.jsp
@@ -0,0 +1,53 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapper"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix"
+         import="org.apache.hadoop.hbase.ipc.MetricsHBaseServerWrapper" %>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  MetricsRegionServerWrapper mWrap = regionServer.getMetrics().getRegionServerWrapper();
+  MetricsHBaseServerWrapper mServerWrap = regionServer.getRpcServer().getMetrics().getHBaseServerWrapper();
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>Compaction Queue Length</th>
+    <th>Flush Queue Length</th>
+    <th>Priority Call Queue Length</th>
+    <th>General Call Queue Length</th>
+    <th>Replication Call Queue Length</th>
+    <th>Total Call Queue Size</th>
+  </tr>
+  <tr>
+    <td><%= mWrap.getCompactionQueueSize() %></td>
+    <td><%= mWrap.getFlushQueueSize() %></td>
+    <td><%= mServerWrap.getPriorityQueueLength() %></td>
+    <td><%= mServerWrap.getGeneralQueueLength() %></td>
+    <td><%= mServerWrap.getReplicationQueueLength() %></td>
+    <td><%= TraditionalBinaryPrefix.long2String(mServerWrap.getTotalQueueSize(), "B", 1) %></td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsRequestStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsRequestStats.jsp
new file mode 100644
index 00000000000..bf435884b3d
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsRequestStats.jsp
@@ -0,0 +1,46 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapper" %>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  MetricsRegionServerWrapper mWrap = regionServer.getMetrics().getRegionServerWrapper();
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>Request Per Second</th>
+    <th>Read Request Count</th>
+    <th>Filtered Read Request Count</th>
+    <th>Write Request Count</th>
+  </tr>
+  <tr>
+    <td><%= String.format("%.0f", mWrap.getRequestsPerSecond()) %></td>
+    <td><%= mWrap.getReadRequestsCount() %></td>
+    <td><%= mWrap.getFilteredReadRequestsCount() %></td>
+    <td><%= mWrap.getWriteRequestsCount() %></td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsStoreStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsStoreStats.jsp
new file mode 100644
index 00000000000..2da3ea0bba0
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsStoreStats.jsp
@@ -0,0 +1,49 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapper"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix" %>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  MetricsRegionServerWrapper mWrap = regionServer.getMetrics().getRegionServerWrapper();
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>Num. Stores</th>
+    <th>Num. Storefiles</th>
+    <th>Root Index Size</th>
+    <th>Index Size</th>
+    <th>Bloom Size</th>
+  </tr>
+  <tr>
+    <td><%= mWrap.getNumStores() %></td>
+    <td><%= mWrap.getNumStoreFiles() %></td>
+    <td><%= TraditionalBinaryPrefix.long2String(mWrap.getStoreFileIndexSize(), "B", 1) %></td>
+    <td><%= TraditionalBinaryPrefix.long2String(mWrap.getTotalStaticIndexSize(), "B", 1) %></td>
+    <td><%= TraditionalBinaryPrefix.long2String(mWrap.getTotalStaticBloomSize(), "B", 1) %></td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsWalStats.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsWalStats.jsp
new file mode 100644
index 00000000000..d172a86feff
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/serverMetricsWalStats.jsp
@@ -0,0 +1,50 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.*"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapper"
+         import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix" %>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  MetricsRegionServerWrapper mWrap = regionServer.getMetrics().getRegionServerWrapper();
+%>
+
+<table class="table table-striped">
+  <tr>
+    <th>Num. WAL Files</th>
+    <th>Size. WAL Files</th>
+    <th>WAL exclude DNs</th>
+  </tr>
+  <tr>
+    <td><%= mWrap.getNumWALFiles() %></td>
+    <td><%= TraditionalBinaryPrefix.long2String(mWrap.getWALFileSize(), "B", 1) %></td>
+    <td>
+      <% for (String exclude: mWrap.getWALExcludeDNs()) { %>
+      <%= exclude %><br>
+      <% } %>
+    </%for>
+    </td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/regionserver/softwareAttributes.jsp b/hbase-server/src/main/resources/hbase-webapps/regionserver/softwareAttributes.jsp
new file mode 100644
index 00000000000..dd46f5eb444
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/regionserver/softwareAttributes.jsp
@@ -0,0 +1,114 @@
+<%--
+/**
+ * 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.
+ */
+--%>
+<%@ page contentType="text/html;charset=UTF-8"
+         import="java.util.*"
+         import="org.apache.hadoop.hbase.util.JvmVersion"
+         import="org.apache.hadoop.hbase.regionserver.HRegionServer"
+         import="org.apache.hadoop.hbase.ServerName"
+         import="org.apache.hadoop.hbase.util.ZKStringFormatter"%>
+
+<%
+  HRegionServer regionServer =
+    (HRegionServer) getServletContext().getAttribute(HRegionServer.REGIONSERVER);
+
+  ServerName masterServerName = (ServerName) request.getAttribute("masterServerName");
+  int infoPort = (int) request.getAttribute("infoPort");
+%>
+<table id="attributes_table" class="table table-striped">
+  <tr>
+    <th>Attribute Name</th>
+    <th>Value</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>JVM Version</td>
+    <td><%= JvmVersion.getVersion() %></td>
+    <td>JVM vendor and version</td>
+  </tr>
+  <tr>
+    <td>HBase Version</td>
+    <td><%= org.apache.hadoop.hbase.util.VersionInfo.getVersion() %>, revision=<%= org.apache.hadoop.hbase.util.VersionInfo.getRevision() %></td>
+    <td>HBase version and revision</td>
+  </tr>
+  <tr>
+    <td>HBase Compiled</td>
+    <td><%= org.apache.hadoop.hbase.util.VersionInfo.getDate() %>, <%= org.apache.hadoop.hbase.util.VersionInfo.getUser() %></td>
+    <td>When HBase version was compiled and by whom</td>
+  </tr>
+  <tr>
+    <td>HBase Source Checksum</td>
+    <td><%= org.apache.hadoop.hbase.util.VersionInfo.getSrcChecksum() %></td>
+    <td>HBase source SHA512 checksum</td>
+  </tr>
+  <tr>
+    <td>Hadoop Version</td>
+    <td><%= org.apache.hadoop.util.VersionInfo.getVersion() %>, revision=<%= org.apache.hadoop.util.VersionInfo.getRevision() %></td>
+    <td>Hadoop version and revision</td>
+  </tr>
+  <tr>
+    <td>Hadoop Compiled</td>
+    <td><%= org.apache.hadoop.util.VersionInfo.getDate() %>, <%= org.apache.hadoop.util.VersionInfo.getUser() %></td>
+    <td>When Hadoop version was compiled and by whom</td>
+  </tr>
+  <tr>
+    <td>Hadoop Source Checksum</td>
+    <td><%= org.apache.hadoop.util.VersionInfo.getSrcChecksum() %></td>
+    <td>Hadoop source MD5 checksum</td>
+  </tr>
+  <tr>
+    <td>ZooKeeper Client Version</td>
+    <td><%= org.apache.zookeeper.Version.getVersion() %>, revision=<%= org.apache.zookeeper.Version.getRevisionHash() %></td>
+    <td>ZooKeeper client version and revision hash</td>
+  </tr>
+  <tr>
+    <td>ZooKeeper Client Compiled</td>
+    <td><%= org.apache.zookeeper.Version.getBuildDate() %></td>
+    <td>When ZooKeeper client version was compiled</td>
+  </tr>
+  <tr>
+    <td>ZooKeeper Quorum</td>
+    <td><%= ZKStringFormatter.formatZKString(regionServer.getZooKeeper()) %></td>
+    <td>Addresses of all registered ZK servers</td>
+  </tr>
+  <tr>
+    <td>Coprocessors</td>
+    <td><%= Arrays.toString(regionServer.getRegionServerCoprocessors()) %></td>
+    <td>Coprocessors currently loaded by this regionserver</td>
+  </tr>
+  <tr>
+    <td>RS Start Time</td>
+    <td><%= new Date(regionServer.getStartcode()) %></td>
+    <td>Date stamp of when this region server was started</td>
+  </tr>
+  <tr>
+    <td>HBase Master</td>
+    <td>
+      <% if (masterServerName == null) { %>
+      No master found
+      <% } else {
+        String host = masterServerName.getHostname() + ":" + infoPort;
+        String url = "//" + host + "/master.jsp";
+      %>
+        <a href="<%= url %>"><%= host %></a>
+      <% } %>
+    </td>
+    <td>Address of HBase Master</td>
+  </tr>
+</table>
diff --git a/hbase-server/src/main/resources/hbase-webapps/static/css/hbase.css b/hbase-server/src/main/resources/hbase-webapps/static/css/hbase.css
index d1ae4ace7f4..46d8ca96f20 100644
--- a/hbase-server/src/main/resources/hbase-webapps/static/css/hbase.css
+++ b/hbase-server/src/main/resources/hbase-webapps/static/css/hbase.css
@@ -152,3 +152,32 @@ table.tablesorter thead tr .tablesorter-headerDesc {
 table.nowrap th, table.nowrap td {
     white-space: nowrap;
 }
+
+/* Block Cache pagination */
+.pagination-container {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 0;
+}
+
+.page-numbers {
+  display: flex;
+  gap: 4px;
+}
+
+.page-number {
+  min-width: 30px;
+  text-align: center;
+}
+
+.page-number.active {
+  font-weight: bold;
+  background-color: #eee;
+}
+
+.page-info {
+  margin-left: 15px;
+  font-size: 0.9em;
+  color: #666;
+}
diff --git a/hbase-server/src/main/resources/hbase-webapps/static/js/blockCacheInit.js b/hbase-server/src/main/resources/hbase-webapps/static/js/blockCacheInit.js
new file mode 100644
index 00000000000..164ad9752e5
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/static/js/blockCacheInit.js
@@ -0,0 +1,120 @@
+/*
+ * Copyright The Apache Software Foundation
+ *
+ * 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.
+ */
+
+/*
+ * This script displays and paginates the initially hidden table rows of Hit Ratios for time periods.
+ * See: HBASE-29276 Compute and display hit ratio by configurable, granular time periods
+ */
+
+// Wait for document to be fully loaded
+document.addEventListener('DOMContentLoaded', function() {
+
+  // Count actual items in the DOM
+  const itemRows = document.querySelectorAll('tr.item-row');
+
+  // Pagination state
+  let currentPage = 1;
+  const pageSize = 10;
+  const totalItems = itemRows.length;
+  const totalPages = Math.ceil(totalItems / pageSize);
+
+  // Create page buttons
+  const pageButtonsContainer = document.getElementById('page-buttons');
+  if (pageButtonsContainer) {
+    for (let i = 1; i <= totalPages; i++) {
+      const button = document.createElement('button');
+      button.className = 'page-number';
+      button.textContent = i;
+      button.onclick = function() { goToPage(i); };
+      pageButtonsContainer.appendChild(button);
+    }
+  }
+  function displayItems() {
+    // Hide all item rows
+    itemRows.forEach(row => {
+      row.style.display = 'none';
+    });
+
+    // Calculate indexes
+    const startIndex = (currentPage - 1) * pageSize;
+    const endIndex = Math.min(startIndex + pageSize, totalItems);
+
+    // Show rows for current page
+    let displayedCount = 0;
+    for (let i = startIndex; i < endIndex; i++) {
+      const row = document.getElementById('row-' + i);
+      if (row) {
+        row.style.display = 'table-row';
+        displayedCount++;
+      }
+    }
+
+    // Update pagination UI
+    document.querySelectorAll('.page-number').forEach(btn => {
+      if (parseInt(btn.textContent) === currentPage) {
+        btn.classList.add('active');
+      } else {
+        btn.classList.remove('active');
+      }
+    });
+
+    const prevBtn = document.getElementById('prev-page');
+    const nextBtn = document.getElementById('next-page');
+
+    if (prevBtn) prevBtn.disabled = currentPage === 1;
+    if (nextBtn) nextBtn.disabled = currentPage === totalPages;
+
+    // Update page info
+    const pageInfo = document.getElementById('page-info');
+    if (pageInfo) {
+      pageInfo.textContent = `Showing ${startIndex + 1} to ${endIndex} of ${totalItems} items`;
+    }
+  }
+
+  function goToPage(page) {
+    if (page >= 1 && page <= totalPages) {
+      currentPage = page;
+      displayItems();
+    }
+  }
+
+  window.nextPage = function() {
+    goToPage(currentPage + 1);
+  };
+
+  window.prevPage = function() {
+    goToPage(currentPage - 1);
+  };
+
+  window.goToPage = goToPage;
+
+  // Check URL for initial page
+  const urlParams = new URLSearchParams(window.location.search);
+  const pageParam = urlParams.get('page');
+  if (pageParam) {
+    const parsedPage = parseInt(pageParam);
+    if (!isNaN(parsedPage) && parsedPage >= 1) {
+      currentPage = parsedPage;
+    }
+  }
+
+  // Initial display
+  displayItems();
+});
diff --git a/hbase-server/src/main/resources/hbase-webapps/static/js/regionServerStatusInit.js b/hbase-server/src/main/resources/hbase-webapps/static/js/regionServerStatusInit.js
new file mode 100644
index 00000000000..f65ffdab876
--- /dev/null
+++ b/hbase-server/src/main/resources/hbase-webapps/static/js/regionServerStatusInit.js
@@ -0,0 +1,86 @@
+/*
+ * Copyright The Apache Software Foundation
+ *
+ * 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.
+ */
+
+$(document).ready(function()
+  {
+    $.tablesorter.addParser({
+      id: 'filesize',
+      is: function(s) {
+        return s.match(new RegExp( /([\.0-9]+)\ (B|KB|MB|GB|TB)/ ));
+      },
+      format: function(s) {
+        var suf = s.match(new RegExp( /(KB|B|GB|MB|TB)$/ ))[1];
+        var num = parseFloat(s.match( new RegExp( /([\.0-9]+)\ (B|KB|MB|GB|TB)/ ))[0]);
+        switch(suf) {
+          case 'B':
+            return num;
+          case 'KB':
+            return num * 1024;
+          case 'MB':
+            return num * 1024 * 1024;
+          case 'GB':
+            return num * 1024 * 1024 * 1024;
+          case 'TB':
+            return num * 1024 * 1024 * 1024 * 1024;
+        }
+      },
+      type: 'numeric'
+    });
+    $.tablesorter.addParser(
+      {
+        id: "separator",
+        is: function (s) {
+          return /^[0-9]?[0-9,]*$/.test(s);
+        },
+        format: function (s) {
+          return $.tablesorter.formatFloat( s.replace(/,/g,'') );
+        },
+        type: "numeric"
+      });
+
+    $("#baseStatsTable").tablesorter({
+      headers: {
+        '.cls_emptyMin': {empty: 'emptyMin'},
+        '.cls_emptyMax': {empty: 'emptyMax'}
+      }
+    });
+    $("#requestStatsTable").tablesorter({
+      headers: {
+        '.cls_separator': {sorter: 'separator'}
+      }
+    });
+    $("#storeStatsTable").tablesorter({
+      headers: {
+        '.cls_separator': {sorter: 'separator'},
+        '.cls_filesize': {sorter: 'filesize'}
+      }
+    });
+    $("#compactionStatsTable").tablesorter({
+      headers: {
+        '.cls_separator': {sorter: 'separator'}
+      }
+    });
+    $("#memstoreStatsTable").tablesorter({
+      headers: {
+        '.cls_filesize': {sorter: 'filesize'}
+      }
+    });
+  }
+);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestInfoServers.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestInfoServers.java
index e0238e41e9b..a894bbcd0ae 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestInfoServers.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestInfoServers.java
@@ -97,7 +97,8 @@ public class TestInfoServers {
     assertContainsContent(new URL("http://localhost:" + port + "/master-status"), "master.jsp");
     port = UTIL.getHBaseCluster().getRegionServerThreads().get(0).getRegionServer().getInfoServer()
       .getPort();
-    assertContainsContent(new URL("http://localhost:" + port + "/index.html"), "rs-status");
+    assertContainsContent(new URL("http://localhost:" + port + "/index.html"), "regionserver.jsp");
+    assertContainsContent(new URL("http://localhost:" + port + "/rs-status"), "regionserver.jsp");
   }
 
   /**
@@ -111,7 +112,7 @@ public class TestInfoServers {
     assertContainsContent(new URL("http://localhost:" + port + "/master.jsp"), "meta");
     port = UTIL.getHBaseCluster().getRegionServerThreads().get(0).getRegionServer().getInfoServer()
       .getPort();
-    assertContainsContent(new URL("http://localhost:" + port + "/rs-status"), "meta");
+    assertContainsContent(new URL("http://localhost:" + port + "/regionserver.jsp"), "meta");
   }
 
   @Test
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusUtil.java
index d66c1b1b2e1..0ec94c91719 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusUtil.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/http/TestMasterStatusUtil.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.hbase.master.http;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
@@ -203,7 +204,7 @@ public class TestMasterStatusUtil {
     conf.setBoolean("hbase.master.ui.fragmentation.enabled", true);
     Map<String, Integer> fragmentationInfo = MasterStatusUtil.getFragmentationInfo(master, conf);
     assertNotNull(fragmentationInfo);
-    assertEquals(1, fragmentationInfo.size());
+    assertFalse(fragmentationInfo.isEmpty());
   }
 
   @Test
@@ -240,7 +241,8 @@ public class TestMasterStatusUtil {
     String link = MasterStatusUtil.serverNameLink(master, FAKE_HOST);
 
     assertNotNull(link);
-    assertEquals("<a href=\"//fakehost:16030/rs-status\">fakehost,12345,1234567890</a>", link);
+    assertEquals("<a href=\"//fakehost:16030/regionserver.jsp\">fakehost,12345,1234567890</a>",
+      link);
   }
 
   @Test
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/http/TestRSStatusPage.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/http/TestRSStatusPage.java
new file mode 100644
index 00000000000..b4b7214b037
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/http/TestRSStatusPage.java
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.regionserver.http;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtil;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.LocalHBaseCluster;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.apache.hadoop.hbase.master.HMaster;
+import org.apache.hadoop.hbase.master.ServerManager;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RegionServerTests;
+import org.apache.hadoop.hbase.util.CommonFSUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestName;
+
+/**
+ * Tests for the region server status page and its template.
+ */
+@Category({ RegionServerTests.class, MediumTests.class })
+public class TestRSStatusPage {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestRSStatusPage.class);
+
+  private static LocalHBaseCluster CLUSTER;
+
+  private final static HBaseTestingUtil UTIL = new HBaseTestingUtil();
+  public static final String TEST_TABLE_NAME_1 = "TEST_TABLE_1";
+  public static final String TEST_TABLE_NAME_2 = "TEST_TABLE_2";
+
+  @Rule
+  public TestName name = new TestName();
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    Configuration conf = UTIL.getConfiguration();
+    UTIL.startMiniZKCluster();
+
+    UTIL.startMiniDFSCluster(1);
+    Path rootdir = UTIL.getDataTestDirOnTestFS("TestRSStatusPage");
+    CommonFSUtils.setRootDir(conf, rootdir);
+
+    // The info servers do not run in tests by default.
+    // Set them to ephemeral ports so they will start
+    // setup configuration
+    conf.setInt(HConstants.MASTER_INFO_PORT, 0);
+    conf.setInt(HConstants.REGIONSERVER_INFO_PORT, 0);
+
+    CLUSTER = new LocalHBaseCluster(conf, 1);
+    CLUSTER.startup();
+    CLUSTER.getActiveMaster().waitForMetaOnline();
+  }
+
+  /**
+   * Helper method to shut down the cluster (if running)
+   */
+  @AfterClass
+  public static void shutDownMiniCluster() throws Exception {
+    if (CLUSTER != null) {
+      CLUSTER.shutdown();
+      CLUSTER.join();
+    }
+    UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testStatusPage() throws Exception {
+    HMaster master = CLUSTER.getActiveMaster();
+
+    int masterPort = master.getActiveMasterManager().getActiveMasterInfoPort();
+    ServerName activeMaster =
+      master.getActiveMaster().orElseThrow(() -> new IllegalStateException("No active master"));
+    String masterHostname = activeMaster.getHostname();
+
+    createTestTables(master);
+
+    ServerManager serverManager = master.getServerManager();
+    List<ServerName> onlineServersList = serverManager.getOnlineServersList();
+
+    assertEquals(1, onlineServersList.size());
+
+    ServerName firstServerName = onlineServersList.get(0);
+    int infoPort = master.getRegionServerInfoPort(firstServerName);
+    String hostname = firstServerName.getHostname();
+    int port = firstServerName.getPort();
+
+    String page = getRegionServerStatusPageContent(hostname, infoPort);
+
+    assertTrue(page.contains("<title>HBase Region Server: " + masterHostname + "</title>"));
+
+    String expectedPageHeader = "<h1>RegionServer <small>" + hostname + "," + port + ","
+      + firstServerName.getStartCode() + "</small></h1>";
+    assertTrue(page.contains(expectedPageHeader));
+    assertTrue(page.contains("<h2>Server Metrics</h2>"));
+    assertTrue(page.contains("<th>Requests Per Second</th>"));
+    assertTrue(page.contains("<h2>Block Cache</h2>"));
+    assertTrue(page.contains("<h2>Regions</h2>"));
+    assertTrue(page.contains("<h2>Replication Status</h2>"));
+    assertTrue(page.contains("<h2>Software Attributes</h2>"));
+
+    // Should have a link to master
+    String expectedMasterLink = "<a href=\"//" + masterHostname + ":" + masterPort
+      + "/master.jsp\">" + masterHostname + ":" + masterPort + "</a>";
+    assertTrue(page.contains(expectedMasterLink));
+  }
+
+  private static void createTestTables(HMaster master) throws IOException {
+    ColumnFamilyDescriptor cf = ColumnFamilyDescriptorBuilder.of("CF");
+    TableDescriptor tableDescriptor1 = TableDescriptorBuilder
+      .newBuilder(TableName.valueOf(TEST_TABLE_NAME_1)).setColumnFamily(cf).build();
+    master.createTable(tableDescriptor1, null, 0, 0);
+    TableDescriptor tableDescriptor2 = TableDescriptorBuilder
+      .newBuilder(TableName.valueOf(TEST_TABLE_NAME_2)).setColumnFamily(cf).build();
+    master.createTable(tableDescriptor2, null, 0, 0);
+    master.flushMasterStore();
+  }
+
+  private static String getRegionServerStatusPageContent(String hostname, int infoPort)
+    throws IOException {
+    URL url = new URL("http://" + hostname + ":" + infoPort + "/regionserver.jsp");
+    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+    conn.connect();
+
+    assertEquals(200, conn.getResponseCode());
+    assertEquals("text/html;charset=utf-8", conn.getContentType());
+
+    return getResponseBody(conn);
+  }
+
+  private static String getResponseBody(HttpURLConnection conn) throws IOException {
+    StringBuilder sb = new StringBuilder();
+    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+    String output;
+    while ((output = br.readLine()) != null) {
+      sb.append(output);
+    }
+    return sb.toString();
+  }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/http/TestRSStatusServlet.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/http/TestRSStatusServlet.java
deleted file mode 100644
index c7e310772a9..00000000000
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/http/TestRSStatusServlet.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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.
- */
-package org.apache.hadoop.hbase.regionserver.http;
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.Optional;
-import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.HBaseConfiguration;
-import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.ServerName;
-import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.client.RegionInfo;
-import org.apache.hadoop.hbase.client.RegionInfoBuilder;
-import org.apache.hadoop.hbase.client.TableDescriptor;
-import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
-import org.apache.hadoop.hbase.io.ByteBuffAllocator;
-import org.apache.hadoop.hbase.ipc.MetricsHBaseServer;
-import org.apache.hadoop.hbase.ipc.MetricsHBaseServerWrapperStub;
-import org.apache.hadoop.hbase.ipc.RpcServerInterface;
-import org.apache.hadoop.hbase.regionserver.HRegionServer;
-import org.apache.hadoop.hbase.regionserver.MetricsRegionServer;
-import org.apache.hadoop.hbase.regionserver.MetricsRegionServerWrapperStub;
-import org.apache.hadoop.hbase.regionserver.RSRpcServices;
-import org.apache.hadoop.hbase.testclassification.RegionServerTests;
-import org.apache.hadoop.hbase.testclassification.SmallTests;
-import org.apache.hadoop.hbase.tmpl.regionserver.RSStatusTmpl;
-import org.apache.hadoop.hbase.util.Bytes;
-import org.apache.hadoop.hbase.zookeeper.MasterAddressTracker;
-import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.TestName;
-import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
-import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
-
-import org.apache.hadoop.hbase.shaded.protobuf.ResponseConverter;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetServerInfoResponse;
-
-/**
- * Tests for the region server status page and its template.
- */
-@Category({ RegionServerTests.class, SmallTests.class })
-public class TestRSStatusServlet {
-
-  @ClassRule
-  public static final HBaseClassTestRule CLASS_RULE =
-    HBaseClassTestRule.forClass(TestRSStatusServlet.class);
-
-  private static final Logger LOG = LoggerFactory.getLogger(TestRSStatusServlet.class);
-  private HRegionServer rs;
-  private RSRpcServices rpcServices;
-  private RpcServerInterface rpcServer;
-
-  static final int FAKE_IPC_PORT = 1585;
-  static final int FAKE_WEB_PORT = 1586;
-
-  private final ServerName fakeServerName = ServerName.valueOf("localhost", FAKE_IPC_PORT, 11111);
-  private final GetServerInfoResponse fakeResponse =
-    ResponseConverter.buildGetServerInfoResponse(fakeServerName, FAKE_WEB_PORT);
-
-  private final ServerName fakeMasterAddress = ServerName.valueOf("localhost", 60010, 1212121212);
-
-  @Rule
-  public TestName name = new TestName();
-
-  @Before
-  public void setupBasicMocks() throws IOException, ServiceException {
-    rs = Mockito.mock(HRegionServer.class);
-    rpcServices = Mockito.mock(RSRpcServices.class);
-    rpcServer = Mockito.mock(RpcServerInterface.class);
-    Mockito.doReturn(HBaseConfiguration.create()).when(rs).getConfiguration();
-    Mockito.doReturn(rpcServices).when(rs).getRSRpcServices();
-    Mockito.doReturn(rpcServer).when(rs).getRpcServer();
-    Mockito.doReturn(fakeResponse).when(rpcServices).getServerInfo(Mockito.any(), Mockito.any());
-    // Fake ZKW
-    ZKWatcher zkw = Mockito.mock(ZKWatcher.class);
-    Mockito.doReturn("fakequorum").when(zkw).getQuorum();
-    Mockito.doReturn(zkw).when(rs).getZooKeeper();
-
-    // Fake BlockCache
-    LOG.warn("The " + HConstants.HFILE_BLOCK_CACHE_SIZE_KEY + " is set to 0");
-    Mockito.doReturn(Optional.empty()).when(rs).getBlockCache();
-
-    // Fake MasterAddressTracker
-    MasterAddressTracker mat = Mockito.mock(MasterAddressTracker.class);
-    Mockito.doReturn(fakeMasterAddress).when(mat).getMasterAddress();
-    Mockito.doReturn(mat).when(rs).getMasterAddressTracker();
-
-    MetricsRegionServer rms = Mockito.mock(MetricsRegionServer.class);
-    Mockito.doReturn(new MetricsRegionServerWrapperStub()).when(rms).getRegionServerWrapper();
-    Mockito.doReturn(rms).when(rs).getMetrics();
-
-    MetricsHBaseServer ms = Mockito.mock(MetricsHBaseServer.class);
-    Mockito.doReturn(new MetricsHBaseServerWrapperStub()).when(ms).getHBaseServerWrapper();
-    Mockito.doReturn(ms).when(rpcServer).getMetrics();
-    Mockito.doReturn(ByteBuffAllocator.HEAP).when(rpcServer).getByteBuffAllocator();
-  }
-
-  @Test
-  public void testBasic() throws IOException, ServiceException {
-    new RSStatusTmpl().render(new StringWriter(), rs);
-  }
-
-  @Test
-  public void testWithRegions() throws IOException, ServiceException {
-    TableDescriptor htd =
-      TableDescriptorBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build();
-    List<RegionInfo> regions = Lists.newArrayList(
-      RegionInfoBuilder.newBuilder(htd.getTableName()).setStartKey(Bytes.toBytes("a"))
-        .setEndKey(Bytes.toBytes("d")).build(),
-      RegionInfoBuilder.newBuilder(htd.getTableName()).setStartKey(Bytes.toBytes("d"))
-        .setEndKey(Bytes.toBytes("z")).build());
-    Mockito.doReturn(ResponseConverter.buildGetOnlineRegionResponse(regions)).when(rpcServices)
-      .getOnlineRegion(Mockito.any(), Mockito.any());
-    new RSStatusTmpl().render(new StringWriter(), rs);
-  }
-}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestZKStringFormatter.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestZKStringFormatter.java
new file mode 100644
index 00000000000..9621344e072
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestZKStringFormatter.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.hbase.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ MiscTests.class, SmallTests.class })
+public class TestZKStringFormatter {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestZKStringFormatter.class);
+
+  private ZKWatcher zkWatcher;
+
+  @Before
+  public void setUp() throws Exception {
+    zkWatcher = mock(ZKWatcher.class);
+  }
+
+  @Test
+  public void testFormatZKStringNullQuorum() {
+    when(zkWatcher.getQuorum()).thenReturn(null);
+    String result = ZKStringFormatter.formatZKString(zkWatcher);
+    assertEquals("", result);
+  }
+
+  @Test
+  public void testFormatZKStringEmptyQuorum() {
+    when(zkWatcher.getQuorum()).thenReturn("");
+    String result = ZKStringFormatter.formatZKString(zkWatcher);
+    assertEquals("", result);
+  }
+
+  @Test
+  public void testFormatZKStringOneServer() {
+    when(zkWatcher.getQuorum()).thenReturn("127.0.0.1:21818");
+    String result = ZKStringFormatter.formatZKString(zkWatcher);
+    assertEquals("127.0.0.1:21818", result);
+  }
+
+  @Test
+  public void testFormatZKStringMultipleServers() {
+    when(zkWatcher.getQuorum()).thenReturn("127.0.0.1:21818,127.0.0.1:21819,127.0.0.1:21820");
+    String result = ZKStringFormatter.formatZKString(zkWatcher);
+    assertEquals("127.0.0.1:21818<br/>127.0.0.1:21819<br/>127.0.0.1:21820", result);
+  }
+}
diff --git a/hbase-shell/src/main/ruby/hbase/taskmonitor.rb b/hbase-shell/src/main/ruby/hbase/taskmonitor.rb
index c9cb042d341..fb2c6874775 100644
--- a/hbase-shell/src/main/ruby/hbase/taskmonitor.rb
+++ b/hbase-shell/src/main/ruby/hbase/taskmonitor.rb
@@ -80,14 +80,16 @@ module Hbase
 
       infoport = @admin.getClusterMetrics.getLiveServerMetrics.get(host).getInfoServerPort.to_s
 
+      tasks_json_path = '/regionserver.jsp?format=json&filter='
+
       begin
         schema = "http://"
-        url = schema + host.hostname + ':' + infoport + '/rs-status?format=json&filter=' + filter
+        url = schema + host.hostname + ':' + infoport + tasks_json_path + filter
         json = URL.new(url).openStream
       rescue SocketException => e
         # Let's try with https when SocketException occur
         schema = "https://"
-        url = schema + host.hostname + ':' + infoport + '/rs-status?format=json&filter=' + filter
+        url = schema + host.hostname + ':' + infoport + tasks_json_path + filter
         json = URL.new(url).openStream
       end