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> - <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>