diff --git a/www/caddy/Makefile b/www/caddy/Makefile index 259bea8b2..1fe46006f 100644 --- a/www/caddy/Makefile +++ b/www/caddy/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= caddy -PLUGIN_VERSION= 1.7.2 +PLUGIN_VERSION= 1.7.3 PLUGIN_DEPENDS= caddy-custom PLUGIN_COMMENT= Modern Reverse Proxy with Automatic HTTPS, Dynamic DNS and Layer4 Routing PLUGIN_MAINTAINER= cedrik@pischem.com diff --git a/www/caddy/pkg-descr b/www/caddy/pkg-descr index d2c94173f..af5015875 100644 --- a/www/caddy/pkg-descr +++ b/www/caddy/pkg-descr @@ -13,6 +13,13 @@ DOC: https://docs.opnsense.org/manual/how-tos/caddy.html Plugin Changelog ================ +1.7.3 + +* Add: Clear All button to Filter by Domain selectpicker +* Change: Layer4 Proxy has own menu entry +* Change: Domains and Subdomains are in same tab, Subdomains are not initially hidden anymore +* Fix: WebGUI port conflict validation triggers too aggressively + 1.7.2 * Add: Directive in HTTP Handler can be chosen, "reverse_proxy" and "redir" diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Layer4Controller.php b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Layer4Controller.php new file mode 100644 index 000000000..00b4548e7 --- /dev/null +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/Layer4Controller.php @@ -0,0 +1,42 @@ +view->pick('OPNsense/Caddy/layer4'); + $this->view->formDialogLayer4 = $this->getForm("dialogLayer4"); + } +} diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php index d62de6c10..fde12d63c 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/ReverseProxyController.php @@ -41,7 +41,6 @@ class ReverseProxyController extends IndexController $this->view->formDialogReverseProxy = $this->getForm("dialogReverseProxy"); $this->view->formDialogSubdomain = $this->getForm("dialogSubdomain"); $this->view->formDialogHandle = $this->getForm("dialogHandle"); - $this->view->formDialogLayer4 = $this->getForm("dialogLayer4"); $this->view->formDialogAccessList = $this->getForm("dialogAccessList"); $this->view->formDialogBasicAuth = $this->getForm("dialogBasicAuth"); $this->view->formDialogHeader = $this->getForm("dialogHeader"); diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml index 43e3fa997..68714b31c 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogHandle.xml @@ -25,7 +25,6 @@ handle.subdomain dropdown - diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogReverseProxy.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogReverseProxy.xml index 46642786b..522c916d4 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogReverseProxy.xml +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogReverseProxy.xml @@ -26,7 +26,7 @@ text example.com - + reverse.FromPort diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/general.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/general.xml index 43afbb0ad..10976f164 100644 --- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/general.xml +++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/general.xml @@ -2,15 +2,20 @@ caddy.general.enabled - + checkbox + + caddy.general.EnableLayer4 + + checkbox + + caddy.general.TlsEmail text - info@example.com @@ -21,12 +26,6 @@ - - caddy.general.EnableLayer4 - - checkbox - - caddy.general.DisableSuperuser diff --git a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/ACL/ACL.xml b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/ACL/ACL.xml index cea3a9cc5..0aaba284c 100644 --- a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/ACL/ACL.xml +++ b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/ACL/ACL.xml @@ -15,6 +15,14 @@ api/caddy/reverse_proxy/* + + Services: Caddy Web Server: Layer4 Proxy + Allow access to Caddy Layer4 Proxy + + ui/caddy/layer4/* + api/caddy/reverse_proxy/* + + Services: Caddy Web Server: Diagnostics Allow access to Caddy Diagnostics diff --git a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Menu/Menu.xml b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Menu/Menu.xml index 70d146ffe..0a992a746 100644 --- a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Menu/Menu.xml +++ b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Menu/Menu.xml @@ -3,8 +3,9 @@ - - + + + diff --git a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/layer4.volt b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/layer4.volt new file mode 100644 index 000000000..d1bfc6cf6 --- /dev/null +++ b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/layer4.volt @@ -0,0 +1,179 @@ +{# + # Copyright (c) 2024 Cedrik Pischem + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + + + + + + + +
+ +
+
+

{{ lang._('Layer4 Routes') }}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ lang._('ID') }}{{ lang._('Enabled') }}{{ lang._('Sequence') }}{{ lang._('Routing Type') }}{{ lang._('Protocol') }}{{ lang._('Local Port') }}{{ lang._('Domain') }}{{ lang._('Matchers') }}{{ lang._('Invert Matchers') }}{{ lang._('Upstream Domain') }}{{ lang._('Upstream Port') }}{{ lang._('Remote IP') }}{{ lang._('Fail Duration') }}{{ lang._('Proxy Protocol') }}{{ lang._('Description') }}{{ lang._('Commands') }}
+ + +
+
+
+
+
+ + +
+
+
+
+ +

+ + + + +
+
+
+ +{{ partial("layout_partials/base_dialog",['fields':formDialogLayer4,'id':'DialogLayer4','label':lang._('Edit Layer4 Route')])}} diff --git a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt index cd7cb0dc3..e98e921a0 100644 --- a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt +++ b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt @@ -35,7 +35,8 @@ del:'/api/caddy/ReverseProxy/delReverseProxy/', toggle:'/api/caddy/ReverseProxy/toggleReverseProxy/', options: { - requestHandler: addDomainFilterToRequest + requestHandler: addDomainFilterToRequest, + rowCount: [4,7,14,20,50,100,-1] } }); @@ -47,19 +48,11 @@ del:'/api/caddy/ReverseProxy/delSubdomain/', toggle:'/api/caddy/ReverseProxy/toggleSubdomain/', options: { - requestHandler: addDomainFilterToRequest + requestHandler: addDomainFilterToRequest, + rowCount: [4,7,14,20,50,100,-1] } }); - $("#reverseLayer4Grid").UIBootgrid({ - search:'/api/caddy/ReverseProxy/searchLayer4/', - get:'/api/caddy/ReverseProxy/getLayer4/', - set:'/api/caddy/ReverseProxy/setLayer4/', - add:'/api/caddy/ReverseProxy/addLayer4/', - del:'/api/caddy/ReverseProxy/delLayer4/', - toggle:'/api/caddy/ReverseProxy/toggleLayer4/', - }); - $("#reverseHandleGrid").UIBootgrid({ search:'/api/caddy/ReverseProxy/searchHandle/', get:'/api/caddy/ReverseProxy/getHandle/', @@ -78,6 +71,9 @@ set:'/api/caddy/ReverseProxy/setAccessList/', add:'/api/caddy/ReverseProxy/addAccessList/', del:'/api/caddy/ReverseProxy/delAccessList/', + options:{ + rowCount: [4,7,14,20,50,100,-1] + } }); $("#basicAuthGrid").UIBootgrid({ @@ -86,6 +82,9 @@ set:'/api/caddy/ReverseProxy/setBasicAuth/', add:'/api/caddy/ReverseProxy/addBasicAuth/', del:'/api/caddy/ReverseProxy/delBasicAuth/', + options:{ + rowCount: [4,7,14,20,50,100,-1] + } }); $("#reverseHeaderGrid").UIBootgrid({ @@ -133,7 +132,7 @@ function loadDomainFilters() { ajaxGet('/api/caddy/ReverseProxy/getAllReverseDomains', null, function(data, status) { let select = $('#reverseFilter'); - select.empty(); // Clear current options + select.empty(); if (status === "success" && data && data.rows) { data.rows.forEach(function(item) { select.append($(''); } - select.selectpicker('refresh'); // Refresh selectpicker to update the UI + select.selectpicker('refresh'); }).fail(function() { $('#reverseFilter').html('').selectpicker('refresh'); }); } /** - * Controls the visibility of the selectpicker for domain filtering. + * Controls the visibility of the selectpicker and add buttons based on the active tab. * * @param {string} tab - The currently active tab. */ - function toggleSelectPicker(tab) { - if (tab === 'handlesTab' || tab === 'domainsTab' || tab === 'subdomainsTab') { + function toggleVisibility(tab) { + if (tab === 'handlesTab' || tab === 'domainsTab') { + $("#addDomainBtn").show(); + $("#addHandleBtn").show(); $('.common-filter').show(); } else { + $("#addDomainBtn").hide(); + $("#addHandleBtn").hide(); $('.common-filter').hide(); } } - /** - * Controls the visibility of add buttons based on the active tab. - * - * @param {string} tab - The currently active tab. - */ - function toggleButtonVisibility(tab) { - if (tab === 'handlesTab' || tab === 'domainsTab') { - $("#addDomainBtn").show(); - $("#addHandleBtn").show(); - } else { - $("#addDomainBtn").hide(); - $("#addHandleBtn").hide(); - } - } - - /** - * Initializes tabs by fetching data and setting visibility. - */ - function initializeTabs() { - ajaxGet('/api/caddy/reverse_proxy/get', null, function(response, status) { - if (status === "success" && response) { - // Check for wildcards in domains to toggle Subdomains tab - const hasWildcard = Object.values(response.caddy.reverseproxy.reverse).some(entry => entry.FromDomain.startsWith('*')); - toggleTabVisibility('#tab-subdomains', hasWildcard); - toggleSubdomainOptions(hasWildcard); - - // Check if Layer 4 is enabled to toggle the Layer 4 tab - const enableLayer4 = response.caddy.general.EnableLayer4 === '1'; - toggleTabVisibility('#tab-layer4', enableLayer4); - } else { - showAlert("{{ lang._('Failed to load data from /api/caddy/reverse_proxy/get') }}", "error"); - } - }).fail(function() { - showAlert("{{ lang._('Failed to load data from /api/caddy/reverse_proxy/get') }}", "error"); - }); - } - - /** - * Toggles the visibility of a specific tab. - * - * @param {string} tabSelector - The jQuery selector for the tab. - * @param {boolean} visible - Whether the tab should be visible. - */ - function toggleTabVisibility(tabSelector, visible) { - let tab = $(tabSelector); - if (visible) { - tab.show(); - } else { - tab.hide(); - if (tab.hasClass('active')) { - $('#tab-domains a').tab('show'); - } - } - } - - /** - * Toggles the visibility of Subdomain options in dialogHandle based on wildcard domain existence. - * - * @param {boolean} hasWildcard - Whether a wildcard domain exists. - */ - function toggleSubdomainOptions(hasWildcard) { - if (hasWildcard) { - $(".Subdomain").closest('tr').show(); - } else { - $(".Subdomain").closest('tr').hide(); - } + function reloadGrids() { + $("#reverseProxyGrid").bootgrid("reload"); + $("#reverseSubdomainGrid").bootgrid("reload"); + $("#reverseHandleGrid").bootgrid("reload"); } // Hide message area when starting new actions @@ -233,23 +174,19 @@ $("#messageArea").hide(); }); - // Adjusting the Reconfigure button to include validation in onPreAction + // Reconfigure button with custom validation $("#reconfigureAct").SimpleActionButton({ onPreAction: function() { const dfObj = new $.Deferred(); - // Perform configuration validation ajaxGet("/api/caddy/service/validate", null, function(data, status) { if (status === "success" && data && data['status'].toLowerCase() === 'ok') { - // If configuration is valid, resolve the Deferred object to proceed dfObj.resolve(); } else { - // If configuration is invalid, show alert and reject the Deferred object showAlert(data['message'], "error"); dfObj.reject(); } }).fail(function(xhr, status, error) { - // On AJAX error, show alert and reject the Deferred object showAlert("{{ lang._('Validation request failed: ') }}" + error, "error"); dfObj.reject(); }); @@ -257,45 +194,43 @@ return dfObj.promise(); }, onAction: function(data, status) { - // Check if the action was successful if (status === "success" && data && data['status'].toLowerCase() === 'ok') { showAlert("{{ lang._('Configuration applied successfully.') }}", "{{ lang._('Apply Success') }}"); - // Update the service control UI for 'caddy' updateServiceControlUI('caddy'); - // Update the Tab visibility - initializeTabs(); } else { showAlert("{{ lang._('Action was not successful or an error occurred.') }}", "error"); } } }); + $("#clearDomains").on("click", function(e) { + e.preventDefault(); + $('#reverseFilter').val([]); + $('#reverseFilter').selectpicker('refresh'); + + reloadGrids(); + }); + // Reload Bootgrid on filter change $('#reverseFilter').on('changed.bs.select', function() { - $("#reverseProxyGrid").bootgrid("reload"); - $("#reverseSubdomainGrid").bootgrid("reload"); - $("#reverseHandleGrid").bootgrid("reload"); + reloadGrids(); }); // Initialize visibility based on the active tab on page load let activeTab = $('#maintabs .active a').attr('href').replace('#', ''); - toggleSelectPicker(activeTab); - toggleButtonVisibility(activeTab); + toggleVisibility(activeTab); // Change event when switching tabs $('#maintabs a').on('click', function (e) { let currentTab = $(this).attr('href').replace('#', ''); - toggleSelectPicker(currentTab); - toggleButtonVisibility(currentTab); + toggleVisibility(currentTab); }); // Add click event listener for "Add HTTP Handler" button $("#addHandleBtn").on("click", function() { if ($('#maintabs .active a').attr('href') === "#handlesTab") { - // Directly open the dialog if already in the Handles tab $("#addReverseHandleBtn").click(); } else { - // Switch to the "Handlers" tab if not already there $('#maintabs a[href="#handlesTab"]').tab('show').one('shown.bs.tab', function(e) { $("#addReverseHandleBtn").click(); }); @@ -347,11 +282,8 @@ } }); - // Initialize tabs, service control and filter selectpicker - initializeTabs(); updateServiceControlUI('caddy'); loadDomainFilters(); - }); @@ -360,8 +292,19 @@ align-items: center; margin-top: 20px; margin-right: 5px; - padding: 0 15px; // Align with the tables + padding: 0 15px; } + + .filter-actions { + display: flex; + flex-direction: column; + align-items: flex-end; + } + + #clearDomains { + margin-top: 5px; + } + .custom-header { font-weight: 800; font-size: 16px; @@ -371,9 +314,7 @@