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

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


The following commit(s) were added to refs/heads/main by this push:
     new 9b283488c4 Add filters to Running Compactions table in monitor (#4986)
9b283488c4 is described below

commit 9b283488c42d5b4aea8af3e8bc27943e845d9940
Author: Dom G. <domgargu...@apache.org>
AuthorDate: Tue Dec 10 15:23:16 2024 -0500

    Add filters to Running Compactions table in monitor (#4986)
    
    * Add filters to Running Compactions table in monitor
---
 .../org/apache/accumulo/monitor/resources/js/ec.js | 148 ++++++++++++++++++++-
 .../org/apache/accumulo/monitor/templates/ec.ftl   |  53 +++++++-
 2 files changed, 195 insertions(+), 6 deletions(-)

diff --git 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/ec.js
 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/ec.js
index 7ca665fe70..9977e6f306 100644
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/ec.js
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/ec.js
@@ -69,6 +69,11 @@ $(document).ready(function () {
     ]
   });
 
+  const hostnameColumnName = 'hostname';
+  const queueNameColumnName = 'queueName';
+  const tableIdColumnName = 'tableId';
+  const durationColumnName = 'duration';
+
   // Create a table for running compactors
   runningTable = $('#runningTable').DataTable({
     "ajax": {
@@ -94,7 +99,8 @@ $(document).ready(function () {
       }
     ],
     "columns": [{
-        "data": "server"
+        "data": "server",
+        "name": hostnameColumnName
       },
       {
         "data": "kind"
@@ -103,10 +109,12 @@ $(document).ready(function () {
         "data": "status"
       },
       {
-        "data": "queueName"
+        "data": "queueName",
+        "name": queueNameColumnName
       },
       {
-        "data": "tableId"
+        "data": "tableId",
+        "name": tableIdColumnName
       },
       {
         "data": "numFiles"
@@ -132,7 +140,8 @@ $(document).ready(function () {
         "data": "lastUpdate"
       },
       {
-        "data": "duration"
+        "data": "duration",
+        "name": durationColumnName
       },
       { // more column settings
         "class": "details-control",
@@ -143,6 +152,127 @@ $(document).ready(function () {
     ]
   });
 
+  function handleFilterKeyup(input, feedbackElement, columnName) {
+    if (isValidRegex(input) || input === '') { // if valid, apply the filter
+      feedbackElement.hide();
+      $(this).removeClass('is-invalid');
+      const isRegex = true;
+      const smartEnabled = false;
+      runningTable
+        .column(`${columnName}:name`)
+        .search(input, isRegex, smartEnabled)
+        .draw();
+    } else { // if invalid, show the warning
+      feedbackElement.show();
+      $(this).addClass('is-invalid');
+    }
+  }
+
+  $('#hostname-filter').on('keyup', function () {
+    handleFilterKeyup.call(this, this.value, $('#hostname-feedback'), 
hostnameColumnName);
+  });
+
+  $('#queue-filter').on('keyup', function () {
+    handleFilterKeyup.call(this, this.value, $('#queue-feedback'), 
queueNameColumnName);
+  });
+
+  $('#tableid-filter').on('keyup', function () {
+    handleFilterKeyup.call(this, this.value, $('#tableid-feedback'), 
tableIdColumnName);
+  });
+
+  $('#duration-filter').on('keyup', function () {
+    runningTable.draw();
+  });
+
+  // Clear Filters button handler
+  $('#clear-filters').on('click', function () {
+    $(this).prop('disabled', true); // disable the clear button
+
+    // set the filter inputs to empty and trigger the keyup event to clear the 
filters
+    $('#hostname-filter').val('').trigger('keyup');
+    $('#queue-filter').val('').trigger('keyup');
+    $('#tableid-filter').val('').trigger('keyup');
+    $('#duration-filter').val('').trigger('keyup');
+
+    $(this).prop('disabled', false); // re-enable the clear
+  });
+
+  // Custom filter function for duration
+  $.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
+    if (settings.nTable.id !== 'runningTable') {
+      return true;
+    }
+
+    const durationColIndex = 
runningTable.column(`${durationColumnName}:name`).index();
+    const durationStr = data[durationColIndex];
+    const durationSeconds = parseDuration(durationStr);
+
+    const input = $('#duration-filter').val().trim();
+    if (input === '') {
+      $('#duration-feedback').hide();
+      return true;
+    }
+
+    const match = validateDurationInput(input);
+    if (!match) {
+      $('#duration-feedback').show();
+      return false;
+    }
+
+    $('#duration-feedback').hide();
+    const operator = match[1];
+    const value = parseInt(match[2]);
+    const unit = match[3];
+    const filterSeconds = convertToSeconds(value, unit);
+
+    switch (operator) {
+    case '>':
+      return durationSeconds > filterSeconds;
+    case '>=':
+      return durationSeconds >= filterSeconds;
+    case '<':
+      return durationSeconds < filterSeconds;
+    case '<=':
+      return durationSeconds <= filterSeconds;
+    default:
+      console.error(`Unexpected operator "${operator}" encountered in duration 
filter.`);
+      return true;
+    }
+  });
+
+  // Helper function to convert duration strings to seconds
+  function convertToSeconds(value, unit) {
+    switch (unit.toLowerCase()) {
+    case 's':
+      return value;
+    case 'm':
+      return value * 60;
+    case 'h':
+      return value * 3600;
+    case 'd':
+      return value * 86400;
+    default:
+      console.error(`Unexpected unit "${unit}" encountered in duration filter. 
Defaulting to seconds.`);
+      return value;
+    }
+  }
+
+  // Helper function to validate duration input. Makes sure that the input is 
in the format of '<operator> <value> <unit>'
+  function validateDurationInput(input) {
+    return input.match(/^([<>]=?)\s*(\d+)([smhd])$/i);
+  }
+
+  /**
+   * @param {number} durationStr duration in milliseconds
+   * @returns duration in seconds
+   */
+  function parseDuration(durationStr) {
+    // Assuming durationStr is in milliseconds
+    const milliseconds = parseInt(durationStr, 10);
+    const seconds = milliseconds / 1000;
+    return seconds;
+  }
+
   // Create a table for compaction coordinator
   coordinatorTable = $('#coordinatorTable').DataTable({
     "ajax": {
@@ -344,3 +474,13 @@ function refreshRunning() {
   // user paging is not reset on reload
   ajaxReloadTable(runningTable);
 }
+
+// Helper function to validate regex
+function isValidRegex(input) {
+  try {
+    new RegExp(input);
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
diff --git 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/ec.ftl
 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/ec.ftl
index 37ac8b4d51..b1bd312acd 100644
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/ec.ftl
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/ec.ftl
@@ -60,8 +60,57 @@
         <div class="row">
           <div class="col-xs-12">
             <table id="runningTable" class="table caption-top table-bordered 
table-striped table-condensed">
-              <caption><span class="table-caption">Running 
Compactions</span>&nbsp;&nbsp;
-                <a href="javascript:refreshRunning();"><span style="font-size: 
1.5em; color: black;" class="bi bi-arrow-repeat"></span></a>
+              <caption>
+                <div class="d-flex justify-content-between align-items-center 
mb-3">
+                  <div>
+                    <span class="table-caption">Running 
Compactions</span>&nbsp;&nbsp;
+                    <a href="javascript:refreshRunning();">
+                      <span style="font-size: 1.5em; color: black;" class="bi 
bi-arrow-repeat"></span>
+                    </a>
+                  </div>
+                </div>
+                <div class="accordion" id="filterAccordion">
+                  <div class="accordion-item">
+                    <h2 class="accordion-header" id="filterHeading">
+                      <button class="accordion-button collapsed" type="button" 
data-bs-toggle="collapse" data-bs-target="#filterCollapse" 
aria-expanded="false" aria-controls="filterCollapse">
+                        Filters
+                      </button>
+                    </h2>
+                    <div id="filterCollapse" class="accordion-collapse 
collapse" aria-labelledby="filterHeading" data-bs-parent="#filterAccordion">
+                      <div class="accordion-body">
+                        <!-- Hostname Filter -->
+                        <div class="mb-3">
+                          <label for="hostname-filter" 
class="form-label">Hostname Filter</label>
+                          <input type="text" id="hostname-filter" 
class="form-control" placeholder="Enter hostname regex">
+                          <small id="hostname-feedback" class="form-text 
text-danger" style="display:none;">Invalid regex pattern</small>
+                        </div>
+                        <!-- Queue Filter -->
+                        <div class="mb-3">
+                          <label for="queue-filter" class="form-label">Queue 
Filter</label>
+                          <input type="text" id="queue-filter" 
class="form-control" placeholder="Enter queue regex">
+                          <small id="queue-feedback" class="form-text 
text-danger" style="display:none;">Invalid regex pattern</small>
+                        </div>
+                        <!-- Table ID Filter -->
+                        <div class="mb-3">
+                          <label for="tableid-filter" class="form-label">Table 
ID Filter</label>
+                          <input type="text" id="tableid-filter" 
class="form-control" placeholder="Enter table ID regex">
+                          <small id="tableid-feedback" class="form-text 
text-danger" style="display:none;">Invalid regex pattern</small>
+                        </div>
+                        <!-- Duration Filter -->
+                        <div class="mb-3">
+                          <label for="duration-filter" 
class="form-label">Duration Filter</label>
+                          <input type="text" id="duration-filter" 
class="form-control" placeholder="Enter duration (e.g., &gt;10m, &lt;1h, 
&gt;=5s, &lt;=2d)">
+                          <small id="duration-feedback" class="form-text 
text-danger" style="display:none;">Invalid duration format</small>
+                          <small class="form-text text-muted">Valid formats: 
&gt;10m, &lt;1h, &gt;=5s, &lt;=2d</small>
+                        </div>
+                        <!-- Clear Filters Button -->
+                        <div class="mb-3">
+                          <button id="clear-filters" class="btn 
btn-secondary">Clear Filters</button>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
               </caption>
               <thead>
                 <tr>

Reply via email to