www/caddy: Selectpicker that can filter the Domain, Subdomain and Handlers by selected Domain (#3937)

* Update reverse_proxy.volt

Add a first version of a filter functionality by domain. In this version, only handlers are filtered by domain.

A selectpicker with multi selection can choose domains, and the filter function compares these UUIDs to the UUIDs of the "reverse" UUIDs of the model relation fields. Either all domains are shown, or only elements where the UUIDs match.

* Update ReverseProxyController.php

A new api endpoint for the domain search selectpicker has been created. It returns the ID and a Domain+Port combination.

The search function for the Handler now returns all fields if no filter has been set, or only the referenced UUIDs when a filter has been set.

* Update ReverseProxyController.php - Add search function to subdomains

* Update reverse_proxy.volt - Reference Search Filter in Handlers, Domains and Subdomains

* Update ReverseProxyController.php - Add the search function to domains

* Update ReverseProxyController.php

A little bit of cleanup. $add_empty was unused so it's removed.

* Update reverse_proxy.volt - Changed margin to align selectpicker with other options.

* Small margin fix to align Selectpicker with other options

* Update reverse_proxy.volt - Restrict style to the ID of only the affected selectpicker
This commit is contained in:
Monviech
2024-04-26 09:41:17 +02:00
committed by GitHub
parent 8ec93bd7c9
commit 2ec0eef440
2 changed files with 162 additions and 10 deletions
@@ -41,9 +41,22 @@ class ReverseProxyController extends ApiMutableModelControllerBase
/*ReverseProxy Section*/
public function searchReverseProxyAction($add_empty = '0')
/*Search Function adjusted for the search filter dropdown*/
public function searchReverseProxyAction()
{
return $this->searchBase("reverseproxy.reverse", null, 'description');
// Get a comma-separated list of UUIDs from the request
$reverseUuids = $this->request->get('reverseUuids');
$uuidArray = !empty($reverseUuids) ? explode(',', $reverseUuids) : [];
// Define the filter function to handle multiple UUIDs
$filterFunction = function ($modelItem) use ($uuidArray) {
$itemUuid = (string)$modelItem->getAttributes()['uuid'];
// Include the item if no UUIDs are provided (empty array) or if it's in the array of UUIDs
return empty($uuidArray) || in_array($itemUuid, $uuidArray, true);
};
// Return the search results filtered by the provided UUIDs, if any
return $this->searchBase("reverseproxy.reverse", null, 'description', $filterFunction);
}
public function setReverseProxyAction($uuid)
@@ -70,13 +83,48 @@ class ReverseProxyController extends ApiMutableModelControllerBase
{
return $this->toggleBase("reverseproxy.reverse", $uuid, $enabled);
}
/*Function for the search filter dropdown in the bootgrid*/
public function getAllReverseDomainsAction()
{
$this->sessionClose(); // Close session early for performance
$result = array("rows" => array());
$mdlCaddy = new \OPNsense\Caddy\Caddy();
$reverseNodes = $mdlCaddy->reverseproxy->reverse->iterateItems();
foreach ($reverseNodes as $item) {
if (!empty($item->FromDomain)) {
// Conditionally concatenate port if it exists
$domain = (string)$item->FromDomain;
$port = (string)$item->FromPort;
$combinedDomainPort = $domain . (!empty($port) ? ':' . $port : '');
$result['rows'][] = array(
'id' => (string)$item->getAttributes()['uuid'],
'domainPort' => $combinedDomainPort // Combined domain and port, conditionally adding port
);
}
}
return $result;
}
/*Subdomain Section*/
public function searchSubdomainAction($add_empty = '0')
/*Search Function adjusted for the search filter dropdown*/
public function searchSubdomainAction()
{
return $this->searchBase("reverseproxy.subdomain", null, 'description');
$reverseUuids = $this->request->get('reverseUuids');
$uuidArray = !empty($reverseUuids) ? explode(',', $reverseUuids) : [];
$filterFunction = function ($modelItem) use ($uuidArray) {
// Filtering on domain UUIDs referenced by subdomains
return empty($uuidArray) || in_array((string)$modelItem->reverse, $uuidArray, true);
};
return $this->searchBase("reverseproxy.subdomain", null, 'description', $filterFunction);
}
public function setSubdomainAction($uuid)
@@ -106,10 +154,25 @@ class ReverseProxyController extends ApiMutableModelControllerBase
/*Handler Section*/
public function searchHandleAction($add_empty = '0')
/*Search Function adjusted for the search filter dropdown*/
public function searchHandleAction()
{
return $this->searchBase("reverseproxy.handle", null, 'description');
$reverseUuids = $this->request->get('reverseUuids');
$uuidArray = explode(',', $reverseUuids);
if (empty($reverseUuids)) {
// If no UUIDs are provided, do not apply any filter, return all records
return $this->searchBase("reverseproxy.handle", null, 'description');
} else {
// Apply the filter only if UUIDs are provided
$filterFunction = function ($modelItem) use ($uuidArray) {
$modelUUID = (string)$modelItem->reverse;
return in_array($modelUUID, $uuidArray, true);
};
return $this->searchBase("reverseproxy.handle", null, 'description', $filterFunction);
}
}
public function setHandleAction($uuid)
@@ -140,7 +203,7 @@ class ReverseProxyController extends ApiMutableModelControllerBase
/* AccessList Section */
public function searchAccessListAction($add_empty = '0')
public function searchAccessListAction()
{
return $this->searchBase("reverseproxy.accesslist", null, 'description');
}
@@ -168,7 +231,7 @@ class ReverseProxyController extends ApiMutableModelControllerBase
/* BasicAuth Section */
public function searchBasicAuthAction($add_empty = '0')
public function searchBasicAuthAction()
{
return $this->searchBase("reverseproxy.basicauth", null, 'description');
}
@@ -216,7 +279,7 @@ class ReverseProxyController extends ApiMutableModelControllerBase
/* Header Section */
public function searchHeaderAction($add_empty = '0')
public function searchHeaderAction()
{
return $this->searchBase("reverseproxy.header", null, 'description');
}
@@ -26,6 +26,17 @@
<script>
$(document).ready(function() {
// Function to handle the search filter request modification
function addDomainFilterToRequest(request) {
var selectedDomains = $('#reverseFilter').val();
if (selectedDomains && selectedDomains.length > 0) {
request['reverseUuids'] = selectedDomains.join(',');
}
return request;
}
// Bootgrid Setup
$("#reverseProxyGrid").UIBootgrid({
search:'/api/caddy/ReverseProxy/searchReverseProxy/',
get:'/api/caddy/ReverseProxy/getReverseProxy/',
@@ -33,6 +44,9 @@
add:'/api/caddy/ReverseProxy/addReverseProxy/',
del:'/api/caddy/ReverseProxy/delReverseProxy/',
toggle:'/api/caddy/ReverseProxy/toggleReverseProxy/',
options: {
requestHandler: addDomainFilterToRequest
}
});
$("#reverseSubdomainGrid").UIBootgrid({
@@ -42,6 +56,9 @@
add:'/api/caddy/ReverseProxy/addSubdomain/',
del:'/api/caddy/ReverseProxy/delSubdomain/',
toggle:'/api/caddy/ReverseProxy/toggleSubdomain/',
options: {
requestHandler: addDomainFilterToRequest
}
});
$("#reverseHandleGrid").UIBootgrid({
@@ -51,6 +68,9 @@
add:'/api/caddy/ReverseProxy/addHandle/',
del:'/api/caddy/ReverseProxy/delHandle/',
toggle:'/api/caddy/ReverseProxy/toggleHandle/',
options: {
requestHandler: addDomainFilterToRequest
}
});
$("#accessListGrid").UIBootgrid({
@@ -143,10 +163,72 @@
// Initialize the service control UI for 'caddy'
updateServiceControlUI('caddy');
// Filter function for domains
function loadDomainFilters() {
$.ajax({
url: '/api/caddy/ReverseProxy/getAllReverseDomains', // custom API endpoint to get uuid and domainport combinations
type: 'GET',
dataType: 'json',
success: function(data) {
var select = $('#reverseFilter');
select.empty(); // Clear current options
if (data && data.rows) {
data.rows.forEach(function(item) {
select.append($('<option>').val(item.id).text(item.domainPort));
});
}
select.selectpicker('refresh'); // Refresh selectpicker to update the UI
},
error: function() {
$('#reverseFilter').html('<option value="">Failed to load data</option>').selectpicker('refresh');
}
});
}
loadDomainFilters();
// Reload Bootgrid on filter change
$('#reverseFilter').on('changed.bs.select', function() {
$("#reverseProxyGrid").bootgrid("reload");
$("#reverseSubdomainGrid").bootgrid("reload");
$("#reverseHandleGrid").bootgrid("reload");
});
// Control the visibility of selectpicker for filter by domain
function toggleSelectPicker(tab) {
if (tab === 'handlesTab' || tab === 'domainsTab') {
$('.common-filter').show();
} else {
$('.common-filter').hide();
}
}
// Initialize visibility based on the active tab on page load
var activeTab = $('#maintabs .active a').attr('href').replace('#', '');
toggleSelectPicker(activeTab);
// Change event when switching tabs
$('#maintabs a').on('click', function (e) {
var currentTab = $(this).attr('href').replace('#', '');
toggleSelectPicker(currentTab);
});
});
</script>
<style>
.common-filter {
text-align: right;
margin-top: 20px;
margin-right: 5px;
padding: 0 15px; // Align with the tables
}
#reverseFilter {
width: 100% !important; // Ensure it fills the container
}
</style>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#domainsTab">Domains</a></li>
<li><a data-toggle="tab" href="#handlesTab">Handlers</a></li>
@@ -156,6 +238,13 @@
<div class="tab-content content-box">
<!-- Selectpicker for filter by domain -->
<div class="form-group common-filter">
<select id="reverseFilter" class="selectpicker form-control" multiple data-live-search="true" data-width="340px" data-size="7" title="Filter by Domain">
<!-- Options will be populated dynamically using JavaScript/Ajax -->
</select>
</div>
<!-- Reverse Proxy Tab -->
<div id="domainsTab" class="tab-pane fade in active">
<div style="padding-left: 16px;">