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. <[email protected]>
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>
- <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>
+ <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., >10m, <1h,
>=5s, <=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:
>10m, <1h, >=5s, <=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>