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

ddanielr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git

commit 2529d83754a7d4f6036f101fd692d289560f0294
Merge: 9c3497bfdc 8779467e73
Author: Daniel Roberts ddanielr <[email protected]>
AuthorDate: Fri Jul 18 02:37:09 2025 +0000

    Merge branch '2.1'

 assemble/pom.xml                                   |  5 ++
 .../org/apache/accumulo/core/conf/Property.java    |  5 ++
 pom.xml                                            |  5 ++
 server/monitor/pom.xml                             |  4 +
 .../apache/accumulo/monitor/EmbeddedWebServer.java | 21 ++++-
 .../java/org/apache/accumulo/monitor/Monitor.java  | 11 ++-
 .../org/apache/accumulo/monitor/view/WebViews.java |  8 +-
 .../accumulo/monitor/resources/js/bulkImport.js    |  2 +-
 .../accumulo/monitor/resources/js/compactions.js   |  4 +-
 .../org/apache/accumulo/monitor/resources/js/ec.js |  8 +-
 .../accumulo/monitor/resources/js/functions.js     | 28 +++----
 .../org/apache/accumulo/monitor/resources/js/gc.js |  2 +-
 .../apache/accumulo/monitor/resources/js/global.js |  2 +
 .../accumulo/monitor/resources/js/manager.js       |  6 +-
 .../apache/accumulo/monitor/resources/js/scans.js  |  4 +-
 .../apache/accumulo/monitor/resources/js/server.js |  4 +-
 .../apache/accumulo/monitor/resources/js/table.js  |  6 +-
 .../accumulo/monitor/resources/js/tservers.js      |  8 +-
 .../apache/accumulo/monitor/templates/default.ftl  | 31 +++----
 .../apache/accumulo/monitor/templates/modals.ftl   |  2 +-
 .../apache/accumulo/monitor/templates/navbar.ftl   | 24 +++---
 .../apache/accumulo/monitor/templates/overview.ftl |  8 +-
 .../apache/accumulo/monitor/templates/tables.ftl   |  4 +-
 .../accumulo/monitor/EmbeddedWebServerTest.java    | 98 ++++++++++++++++++++++
 24 files changed, 227 insertions(+), 73 deletions(-)

diff --cc core/src/main/java/org/apache/accumulo/core/conf/Property.java
index 0f47eee39e,625cb0ed1f..6526d04565
--- a/core/src/main/java/org/apache/accumulo/core/conf/Property.java
+++ b/core/src/main/java/org/apache/accumulo/core/conf/Property.java
@@@ -885,18 -1037,46 +885,23 @@@ public enum Property 
            + " The resources that are used by default can be seen in"
            + " 
`accumulo/server/monitor/src/main/resources/templates/default.ftl`.",
        "2.0.0"),
 +  MONITOR_FETCH_TIMEOUT("monitor.fetch.timeout", "5m", 
PropertyType.TIMEDURATION,
 +      "The Monitor fetches information for display in a set of background 
threads. This property"
 +          + " controls the amount of time that process should wait before 
cancelling any remaining"
 +          + " tasks to fetch information. These background threads could end 
up waiting on servers"
 +          + " to respond or for scans to complete.",
 +      "4.0.0"),
 +  MONITOR_DEAD_LIST_RG_EXCLUSIONS("monitor.dead.server.rg.exclusions", "", 
PropertyType.STRING,
 +      "The Monitor displays information about servers that it believes have 
died recently."
 +          + " This property accepts a comma separated list of resource group 
names. If"
 +          + " the dead servers resource group matches a resource group in 
this list,"
 +          + " then it will be suppressed from the dead servers list in the 
monitor.",
 +      "4.0.0"),
+   MONITOR_ROOT_CONTEXT("monitor.root.context", "/", PropertyType.STRING,
 -      "The root context path of the monitor application. If this value is 
set, all paths for the"
++      "The root context path of the monit   application. If this value is 
set, all paths for the"
+           + " monitor application will be hosted using this context. As an 
example, setting this to `/accumulo/`"
+           + " would cause all `/rest/` endpoints to be hosted at 
`/accumulo/rest/*`.",
+       "2.1.4"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_PREFIX("trace.", null, PropertyType.PREFIX,
 -      "Properties in this category affect the behavior of distributed 
tracing.", "1.3.5"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_SPAN_RECEIVERS("trace.span.receivers", 
"org.apache.accumulo.tracer.ZooTraceClient",
 -      PropertyType.CLASSNAMELIST, "A list of span receiver classes to send 
trace spans.", "1.7.0"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_SPAN_RECEIVER_PREFIX("trace.span.receiver.", null, 
PropertyType.PREFIX,
 -      "Prefix for span receiver configuration properties.", "1.7.0"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_ZK_PATH("trace.zookeeper.path", Constants.ZTRACERS, 
PropertyType.STRING,
 -      "The zookeeper node where tracers are registered.", "1.7.0"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_PORT("trace.port.client", "12234", PropertyType.PORT,
 -      "The listening port for the trace server.", "1.3.5"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_TABLE("trace.table", "trace", PropertyType.STRING,
 -      "The name of the table to store distributed traces.", "1.3.5"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_USER("trace.user", "root", PropertyType.STRING,
 -      "The name of the user to store distributed traces.", "1.3.5"),
 -  @Sensitive
 -  @Deprecated(since = "2.1.0")
 -  TRACE_PASSWORD("trace.password", "secret", PropertyType.STRING,
 -      "The password for the user used to store distributed traces.", "1.3.5"),
 -  @Sensitive
 -  @Deprecated(since = "2.1.0")
 -  TRACE_TOKEN_PROPERTY_PREFIX("trace.token.property.", null, 
PropertyType.PREFIX,
 -      "The prefix used to create a token for storing distributed traces. For"
 -          + " each property required by trace.token.type, place this prefix 
in front of it.",
 -      "1.5.0"),
 -  @Deprecated(since = "2.1.0")
 -  TRACE_TOKEN_TYPE("trace.token.type", PasswordToken.class.getName(), 
PropertyType.CLASSNAME,
 -      "An AuthenticationToken type supported by the authorizer.", "1.5.0"),
 -
    // per table properties
    TABLE_PREFIX("table.", null, PropertyType.PREFIX,
        "Properties in this category affect tablet server treatment of tablets,"
diff --cc pom.xml
index 837e5d7cbe,fdcab6190e..e7c235c160
--- a/pom.xml
+++ b/pom.xml
@@@ -322,10 -322,21 +322,15 @@@
          <artifactId>commons-logging</artifactId>
          <version>1.3.5</version>
        </dependency>
+       <dependency>
+         <groupId>commons-validator</groupId>
+         <artifactId>commons-validator</artifactId>
+         <version>1.10.0</version>
+       </dependency>
 -      <dependency>
 -        <!-- legacy junit version specified here for dependency convergence 
-->
 -        <groupId>junit</groupId>
 -        <artifactId>junit</artifactId>
 -        <version>4.13.2</version>
 -      </dependency>
        <dependency>
          <groupId>org.apache.accumulo</groupId>
 -        <artifactId>accumulo-compaction-coordinator</artifactId>
 -        <version>${project.version}</version>
 +        <artifactId>accumulo-access</artifactId>
 +        <version>${version.accumulo-access}</version>
        </dependency>
        <dependency>
          <groupId>org.apache.accumulo</groupId>
diff --cc server/monitor/pom.xml
index 2fc8822fc0,23ecd0d691..a9cf60ede1
--- a/server/monitor/pom.xml
+++ b/server/monitor/pom.xml
@@@ -56,10 -52,10 +56,14 @@@
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
      </dependency>
+     <dependency>
+       <groupId>commons-validator</groupId>
+       <artifactId>commons-validator</artifactId>
+     </dependency>
 +    <dependency>
 +      <groupId>io.micrometer</groupId>
 +      <artifactId>micrometer-core</artifactId>
 +    </dependency>
      <dependency>
        <groupId>jakarta.inject</groupId>
        <artifactId>jakarta.inject-api</artifactId>
diff --cc server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java
index ba5a3a858e,f9672ed196..e77a9171dd
--- a/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java
+++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java
@@@ -102,8 -105,8 +102,9 @@@ import org.glassfish.jersey.servlet.Ser
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
+ import com.google.common.base.Preconditions;
  import com.google.common.base.Suppliers;
 +import com.google.common.net.HostAndPort;
  
  /**
   * Serve manager statistics with an embedded web server.
@@@ -397,27 -503,28 +402,31 @@@ public class Monitor extends AbstractSe
      }
      HostAndPort monitorHostAndPort = getAdvertiseAddress();
      log.debug("Using {} to advertise monitor location in ZooKeeper", 
monitorHostAndPort);
 +
      try {
 -      
monitorLock.replaceLockData(monitorHostAndPort.toString().getBytes(UTF_8));
 -    } catch (KeeperException | InterruptedException e) {
 -      throw new IllegalStateException("Exception updating monitor lock with 
host and port", e);
 +      getMonitorLock(monitorHostAndPort);
 +    } catch (Exception e) {
 +      log.error("Failed to get Monitor ZooKeeper lock");
 +      throw new RuntimeException(e);
      }
 +    getContext().setServiceLock(monitorLock);
  
      MetricsInfo metricsInfo = getContext().getMetricsInfo();
 +    metricsInfo.addMetricsProducers(this);
      metricsInfo.init(MetricsInfo.serviceTags(getContext().getInstanceName(), 
getApplicationName(),
 -        monitorHostAndPort, ""));
 +        monitorHostAndPort, getResourceGroup()));
  
+     // Needed to support the existing zk monitor address format
+     if (!rootContext.endsWith("/")) {
+       rootContext = rootContext + "/";
+     }
      try {
        URL url = new URL(server.isSecure() ? "https" : "http", 
monitorHostAndPort.getHost(),
-           server.getPort(), "/");
+           server.getPort(), rootContext);
 -      final String path = context.getZooKeeperRoot() + 
Constants.ZMONITOR_HTTP_ADDR;
 -      final ZooReaderWriter zoo = context.getZooReaderWriter();
 +      final ZooReaderWriter zoo = context.getZooSession().asReaderWriter();
        // Delete before we try to re-create in case the previous session 
hasn't yet expired
 -      zoo.delete(path);
 -      zoo.putEphemeralData(path, url.toString().getBytes(UTF_8));
 +      zoo.delete(Constants.ZMONITOR_HTTP_ADDR);
 +      zoo.putEphemeralData(Constants.ZMONITOR_HTTP_ADDR, 
url.toString().getBytes(UTF_8));
        log.info("Set monitor address in zookeeper to {}", url);
      } catch (Exception ex) {
        log.error("Unable to advertise monitor HTTP address in zookeeper", ex);
diff --cc 
server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/bulkImport.js
index b5c0e18912,a89bc95ad4..28a1883c90
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/bulkImport.js
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/bulkImport.js
@@@ -37,9 -38,9 +37,9 @@@ function refresh() 
  /**
   * Initializes the bulk import DataTables
   */
 -$(document).ready(function () {
 +$(function () {
  
-   const url = '/rest/bulkImports';
+   const url = contextPath + 'rest/bulkImports';
    console.debug('REST url used to fetch data for the DataTables in 
bulkImport.js: ' + url);
  
    // Generates the manager bulk import status table
diff --cc 
server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js
index 639d935cf8,30705be653..8a4db14119
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js
@@@ -444,32 -445,17 +444,32 @@@ function getTableServers(tableID) 
  }
  
  /**
 - * REST GET call for the logs, stores it on a sessionStorage variable
 + * REST GET call for the server status, stores it on a sessionStorage variable
   */
 -function getLogs() {
 -  return getJSONForTable(contextPath + 'rest/logs', 'logs');
 +function getStatus() {
-   return getJSONForTable('/rest/status', 'status');
++  return getJSONForTable(contextPath + 'rest/status', 'status');
  }
  
 +/*
 + * Jquery call to clear all data from cells of a table
 + */
 +function clearAllTableCells(tableId) {
 +  console.log("Clearing all table cell data for " + tableId);
 +  $("#" + tableId + " > tbody > tr > td").each(function () {
 +    $(this).text("");
 +  });
 +}
 +
 +// NEW REST CALLS
 +
- const REST_V2_PREFIX = '/rest-v2';
++const REST_V2_PREFIX = contextPath + 'rest-v2';
 +
  /**
 - * REST POST call to clear logs
 + * REST GET call for /problems,
 + * stores it on a sessionStorage variable
   */
 -function clearLogs() {
 -  doLoggedPostCall(contextPath + 'rest/logs/clear', refresh, false);
 +function getProblems() {
 +  return getJSONForTable(REST_V2_PREFIX + '/problems', 'problems');
  }
  
  /**
diff --cc 
server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/table.js
index 0734b573f6,9a37094149..0dd150c89e
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/table.js
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/table.js
@@@ -35,17 -34,24 +35,17 @@@ function refresh() 
    refreshTable();
  }
  
 -function getQueuedAndRunning(data) {
 -  return `${data.running}(${data.queued})`;
 -}
 -
  /**
 - * Initialize the table
 - * 
 - * @param {String} tableID the accumulo table ID
 + * Makes the REST call to fetch tablet details and render them.
   */
 -function initTableServerTable(tableID) {
 -
 -  const url = contextPath + 'rest/tables/' + tableID;
 -  console.debug('REST url used to fetch data for table.js DataTable: ' + url);
 +function initTabletsTable(tableId) {
-   var tabletsUrl = '/rest-v2/tables/' + tableId + '/tablets';
++  var tabletsUrl = contextPath + 'rest-v2/tables/' + tableId + '/tablets';
 +  console.debug('Fetching tablets info from: ' + tabletsUrl);
  
 -  tableServersTable = $('#participatingTServers').DataTable({
 +  tabletsTable = $('#tabletsList').DataTable({
      "ajax": {
 -      "url": url,
 -      "dataSrc": "servers"
 +      "url": tabletsUrl,
 +      "dataSrc": ""
      },
      "stateSave": true,
      "columnDefs": [{
@@@ -68,128 -74,111 +68,128 @@@
        }
      ],
      "columns": [{
 -        "data": "hostname",
 -        "type": "html",
 -        "render": function (data, type, row) {
 -          if (type === 'display') {
 -            data = `<a href="tservers?s=${row.id}">${data}</a>`;
 -          }
 -          return data;
 -        }
 +        "data": "tabletId",
 +        "title": "Tablet ID"
        },
        {
 -        "data": "tablets"
 +        "data": "estimatedSize",
 +        "title": "Estimated Size"
        },
        {
 -        "data": "lastContact"
 +        "data": "estimatedEntries",
 +        "title": "Estimated Entries"
        },
        {
 -        "data": "entries"
 +        "data": "tabletAvailability",
 +        "title": "Availability"
        },
        {
 -        "data": "ingest"
 +        "data": "numFiles",
 +        "title": "Files"
        },
        {
 -        "data": "query"
 +        "data": "numWalLogs",
 +        "title": "WALs"
        },
        {
 -        "data": "holdtime"
 +        "data": "location",
 +        "title": "Location"
 +      }
 +    ]
 +  });
 +}
 +
 +/**
 + * Initialize the table
-  * 
++ *
 + * @param {String} tableId the accumulo table ID
 + */
 +function initTableServerTable(tableId) {
-   const url = '/rest-v2/tables/' + tableId;
++  const url = contextPath + 'rest-v2/tables/' + tableId;
 +  console.debug('REST url used to fetch summary data: ' + url);
 +
 +  tableServersTable = $('#participatingTServers').DataTable({
 +    "ajax": {
 +      "url": url,
 +      "dataSrc": function (json) {
 +        // Convert the JSON object into an array for DataTables consumption.
 +        return [json];
 +      }
 +    },
 +    "stateSave": true,
 +    "searching": false,
 +    "paging": false,
 +    "info": false,
 +    "columnDefs": [{
 +        "targets": "big-num",
 +        "render": function (data, type) {
 +          if (type === 'display') {
 +            data = bigNumberForQuantity(data);
 +          }
 +          return data;
 +        }
        },
        {
 -        "data": function (row) {
 -          return getQueuedAndRunning(row.compactions.scans);
 +        "targets": "big-size",
 +        "render": function (data, type) {
 +          if (type === 'display') {
 +            data = bigNumberForSize(data);
 +          }
 +          return data;
          }
 +      }
 +    ],
 +    "columns": [{
 +        "data": "totalEntries",
 +        "title": "Entry Count"
        },
        {
 -        "data": function (row) {
 -          return getQueuedAndRunning(row.compactions.minor);
 -        }
 +        "data": "totalSizeOnDisk",
 +        "title": "Size on disk"
        },
        {
 -        "data": function (row) {
 -          return getQueuedAndRunning(row.compactions.major);
 -        }
 +        "data": "totalFiles",
 +        "title": "File Count"
        },
        {
 -        "data": "indexCacheHitRate"
 +        "data": "totalWals",
 +        "title": "WAL Count"
        },
        {
 -        "data": "dataCacheHitRate"
 +        "data": "totalTablets",
 +        "title": "Total Tablet Count"
        },
        {
 -        "data": "osload"
 +        "data": "availableAlways",
 +        "title": "Always Hosted Count"
        },
        {
 -        "data": "scansRunning",
 -        "visible": false
 +        "data": "availableOnDemand",
 +        "title": "On Demand Count"
        },
        {
 -        "data": "scansQueued",
 -        "visible": false
 +        "data": "availableNever",
 +        "title": "Never Hosted Count"
        },
        {
 -        "data": "minorRunning",
 -        "visible": false
 +        "data": "totalAssignedTablets",
 +        "title": "Assigned Tablet Count"
        },
        {
 -        "data": "minorQueued",
 -        "visible": false
 +        "data": "totalAssignedToDeadServerTablets",
 +        "title": "Tablets Assigned to Dead Servers Count"
        },
        {
 -        "data": "majorRunning",
 -        "visible": false
 +        "data": "totalHostedTablets",
 +        "title": "Hosted Tablet Count"
        },
        {
 -        "data": "majorQueued",
 -        "visible": false
 +        "data": "totalSuspendedTablets",
 +        "title": "Suspended Tablet Count"
 +      },
 +      {
 +        "data": "totalUnassignedTablets",
 +        "title": "Unassigned Tablet Count"
        }
      ]
    });
diff --cc 
server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl
index 92b70794de,037001569e..3d802c533c
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl
@@@ -30,13 -31,23 +31,13 @@@
          ${val}
        </#list>
      <#else>
-       <script src="/resources/external/jquery/jquery-3.7.1.js"></script>
-       <script 
src="/resources/external/bootstrap/js/bootstrap.bundle.js"></script>
-       <script 
src="/resources/external/datatables/js/jquery.dataTables.js"></script>
-       <script 
src="/resources/external/datatables/js/dataTables.bootstrap5.js"></script>
-       <link rel="stylesheet" 
href="/resources/external/bootstrap/css/bootstrap.css" />
-       <link rel="stylesheet" 
href="/resources/external/bootstrap/css/bootstrap-icons.css" />
-       <link rel="stylesheet" 
href="/resources/external/datatables/css/dataTables.bootstrap5.css" />
+       <script src="resources/external/jquery/jquery-3.7.1.js"></script>
+       <script 
src="resources/external/bootstrap/js/bootstrap.bundle.js"></script>
+       <script 
src="resources/external/datatables/js/jquery.dataTables.js"></script>
+       <script 
src="resources/external/datatables/js/dataTables.bootstrap5.js"></script>
 -      <script src="resources/external/flot/jquery.canvaswrapper.js"></script>
 -      <script src="resources/external/flot/jquery.colorhelpers.js"></script>
 -      <script src="resources/external/flot/jquery.flot.js"></script>
 -      <script src="resources/external/flot/jquery.flot.saturated.js"></script>
 -      <script src="resources/external/flot/jquery.flot.browser.js"></script>
 -      <script 
src="resources/external/flot/jquery.flot.drawSeries.js"></script>
 -      <script 
src="resources/external/flot/jquery.flot.uiConstants.js"></script>
 -      <script src="resources/external/flot/jquery.flot.legend.js"></script>
 -      <script src="resources/external/flot/jquery.flot.time.js"></script>
 -      <script src="resources/external/flot/jquery.flot.resize.js"></script>
+       <link rel="stylesheet" 
href="resources/external/bootstrap/css/bootstrap.css" />
+       <link rel="stylesheet" 
href="resources/external/bootstrap/css/bootstrap-icons.css" />
+       <link rel="stylesheet" 
href="resources/external/datatables/css/dataTables.bootstrap5.css" />
      </#if>
  
      <!-- accumulo resources -->
@@@ -54,11 -65,10 +55,11 @@@
        });
      </script>
      <#if js??>
-       <script src="/resources/js/${js}"></script>
+       <script src="resources/js/${js}"></script>
      </#if>
-     <script src="/resources/js/navbar.js"></script>
-     <script src="/resources/js/systemAlert.js"></script>
-     <script src="/resources/js/modals.js"></script>
+     <script src="resources/js/navbar.js"></script>
+     <script src="resources/js/systemAlert.js"></script>
++    <script src="resources/js/modals.js"></script>
    </head>
  
    <body>
diff --cc 
server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/navbar.ftl
index 7135936d88,7e77c4c3fe..b6424668ee
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/navbar.ftl
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/navbar.ftl
@@@ -52,10 -52,20 +52,10 @@@
                  Activity
                </a>
                <ul class="dropdown-menu col-xs-12" 
aria-labelledby="navbarDropdown">
-                 <li><a class="dropdown-item" 
href="/compactions">Active&nbsp;Compactions</a></li>
-                 <li><a class="dropdown-item" 
href="/scans">Active&nbsp;Scans</a></li>
-                 <li><a class="dropdown-item" 
href="/bulkImports">Bulk&nbsp;Imports</a></li>
-                 <li><a class="dropdown-item" 
href="/ec">External&nbsp;Compactions</a></li>
+                 <li><a class="dropdown-item" 
href="compactions">Active&nbsp;Compactions</a></li>
+                 <li><a class="dropdown-item" 
href="scans">Active&nbsp;Scans</a></li>
+                 <li><a class="dropdown-item" 
href="bulkImports">Bulk&nbsp;Imports</a></li>
+                 <li><a class="dropdown-item" 
href="ec">External&nbsp;Compactions</a></li>
 -                <li><a class="dropdown-item" 
href="replication">Replication</a></li>
 -              </ul>
 -            </li>
 -            <li class="dropdown">
 -              <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown"
 -              role="button" data-bs-toggle="dropdown" 
aria-expanded="false">Debug&nbsp;<span id="errorsNotification" 
class="badge"></span><span class="caret"></span>
 -              </a>
 -              <ul class="dropdown-menu">
 -                <li><a class="dropdown-item" 
href="log">Recent&nbsp;Logs&nbsp;<span id="recentLogsNotifications" 
class="badge"></span></a></li>
 -                <li><a class="dropdown-item" 
href="problems">Table&nbsp;Problems&nbsp;<span id="tableProblemsNotifications" 
class="badge"></span></a></li>
                </ul>
              </li>
              <li class="dropdown">
diff --cc 
server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/tables.ftl
index dcbfc71900,c010c68dae..8f481388cc
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/tables.ftl
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/tables.ftl
@@@ -29,13 -29,8 +29,13 @@@
  
            tableList = $('#tableList').DataTable({
              "ajax": {
-               "url": "/rest-v2/tables",
 -              "url": '${rootContext}rest/tables',
 -              "dataSrc": "table"
++              "url": '${rootContext}rest-v2/tables',
 +              "dataSrc": function (json) {
 +                return Object.keys(json).map(function (key) {
 +                  json[key].tableId = key;
 +                  return json[key];
 +                });
 +              }
              },
              "stateSave": true,
              "columnDefs": [
@@@ -64,7 -79,7 +64,7 @@@
                  "type": "html",
                  "render": function (data, type, row, meta) {
                    if (type === 'display') {
-                     data = '<a href="/tables/' + row.tableId + '">' + 
row.tableName + '</a>';
 -                    data = '<a href="tables/' + row.tableId + '">' + 
row.tablename + '</a>';
++                    data = '<a href="tables/' + row.tableId + '">' + 
row.tableName + '</a>';
                    }
                    return data;
                  }
diff --cc 
server/monitor/src/test/java/org/apache/accumulo/monitor/EmbeddedWebServerTest.java
index 0000000000,1b1cd90b0c..188db48225
mode 000000,100644..100644
--- 
a/server/monitor/src/test/java/org/apache/accumulo/monitor/EmbeddedWebServerTest.java
+++ 
b/server/monitor/src/test/java/org/apache/accumulo/monitor/EmbeddedWebServerTest.java
@@@ -1,0 -1,93 +1,98 @@@
+ /*
+  * 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
+  *
+  *   https://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.accumulo.monitor;
+ 
+ import static org.easymock.EasyMock.createMock;
+ import static org.easymock.EasyMock.expect;
+ import static org.easymock.EasyMock.replay;
+ import static org.easymock.EasyMock.verify;
+ import static org.junit.jupiter.api.Assertions.assertEquals;
+ import static org.junit.jupiter.api.Assertions.assertThrows;
+ 
+ import java.util.concurrent.atomic.AtomicReference;
+ 
+ import org.apache.accumulo.core.conf.ConfigurationCopy;
+ import org.apache.accumulo.core.conf.DefaultConfiguration;
+ import org.apache.accumulo.core.conf.Property;
+ import org.apache.accumulo.server.ServerContext;
++import org.eclipse.jetty.io.ConnectionStatistics;
+ import org.junit.jupiter.api.AfterAll;
+ import org.junit.jupiter.api.BeforeAll;
+ import org.junit.jupiter.api.Test;
+ 
+ /**
+  * Basic tests for EmbeddedWebServer
+  */
+ public class EmbeddedWebServerTest {
+ 
+   private static final AtomicReference<Monitor> monitor = new 
AtomicReference<>(null);
+ 
+   private static final AtomicReference<ConfigurationCopy> configuration = new 
AtomicReference<>();
+ 
+   @BeforeAll
+   public static void createMocks() {
+ 
+     // Mock a configuration with the new context Path
+     ConfigurationCopy config = new 
ConfigurationCopy(DefaultConfiguration.getInstance());
+     config.set(Property.MONITOR_ROOT_CONTEXT, "/test/");
+     configuration.set(config);
+ 
+     ServerContext contextMock = createMock(ServerContext.class);
+     expect(contextMock.getConfiguration()).andReturn(config).atLeastOnce();
+ 
+     Monitor monitorMock = createMock(Monitor.class);
+     expect(monitorMock.getContext()).andReturn(contextMock).atLeastOnce();
++    ConnectionStatistics connectionStatisticMock = 
createMock(ConnectionStatistics.class);
++    expect(connectionStatisticMock.isRunning()).andReturn(true).atLeastOnce();
++    
expect(monitorMock.getConnectionStatisticsBean()).andReturn(connectionStatisticMock)
++        .atLeastOnce();
+     expect(monitorMock.getConfiguration()).andReturn(config).atLeastOnce();
+     
expect(monitorMock.getBindAddress()).andReturn("localhost:9995").atLeastOnce();
+ 
 -    replay(contextMock, monitorMock);
++    replay(contextMock, monitorMock, connectionStatisticMock);
+     monitor.set(monitorMock);
+   }
+ 
+   @AfterAll
+   public static void finishMocks() {
+     Monitor m = monitor.get();
 -    verify(m.getContext(), m);
++    verify(m.getContext(), m, m.getConnectionStatisticsBean());
+   }
+ 
+   @Test
+   public void testContextPath() {
+     // Test removal of trailing slash
+     EmbeddedWebServer ews = new EmbeddedWebServer(monitor.get(),
+         Integer.parseInt(Property.MONITOR_PORT.getDefaultValue()));
+     assertEquals("/test", ews.getContextPath(),
+         "Context path of " + ews.getContextPath() + " does not match");
+     // Test redirect URL
+     configuration.get().set(Property.MONITOR_ROOT_CONTEXT, "/../test");
+     IllegalArgumentException exception =
+         assertThrows(IllegalArgumentException.class, () -> new 
EmbeddedWebServer(monitor.get(),
+             Integer.parseInt(Property.MONITOR_PORT.getDefaultValue())));
+     assertEquals("Root context: \"/../test\" is not a valid URL", 
exception.getMessage());
+     // Test whitespace in URL
+     configuration.get().set(Property.MONITOR_ROOT_CONTEXT, "/whitespace 
/test");
+     exception =
+         assertThrows(IllegalArgumentException.class, () -> new 
EmbeddedWebServer(monitor.get(),
+             Integer.parseInt(Property.MONITOR_PORT.getDefaultValue())));
+     assertEquals("Root context: \"/whitespace /test\" is not a valid URL", 
exception.getMessage());
+   }
+ }

Reply via email to