mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
www/caddy: Put Layer4 Proxy into own menu, cleanup reverse_proxy.volt (#4312)
* www/caddy: Move Layer4 tab to own Menu entry to make it less convoluted. * www/caddy: Remove stray data-column-id * www/caddy: Fix ACL for Layer4 menu * www/caddy: Cleanup terminology of previous * www/caddy: Rename Layer4 to Layer4 Proxy * www/caddy: Cleanup reverse_proxy.volt. Remove Subdomain tab and add it to the Domains tab. Remove some of the hide logic for subdomains for brevity. Add clear all button for filter by domain selectpicker. * www/caddy: Adjust helptext and remove style that is no longer needed due to change where subdomains are displayed * www/caddy: Changelog and version bump * www/caddy: Remove stray data-column-id in handle bootgrid, again
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2023-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.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OPNsense\Caddy;
|
||||
|
||||
use OPNsense\Base\IndexController;
|
||||
|
||||
class Layer4Controller extends IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->pick('OPNsense/Caddy/layer4');
|
||||
$this->view->formDialogLayer4 = $this->getForm("dialogLayer4");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
<id>handle.subdomain</id>
|
||||
<label>Subdomain</label>
|
||||
<type>dropdown</type>
|
||||
<style>Subdomain</style>
|
||||
<help><![CDATA[Select a subdomain to handle. Make sure to additionaly choose a wildcard domain as "Domain". Leave unset, if not using subdomains.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<label>Domain</label>
|
||||
<type>text</type>
|
||||
<hint>example.com</hint>
|
||||
<help><![CDATA[Enter a domain name. For a base domain, use "example.com" or "opn.example.com". For a wildcard domain, use "*.example.com". Adding a wildcard domain and pressing "Apply" will activate the "Subdomain" Tab, in which subdomains can be configured. Using subdomains requires a "DNS Provider" and the "DNS-01 Challenge" or a custom certificate.]]></help>
|
||||
<help><![CDATA[Enter a domain name. For a base domain, use "example.com" or "opn.example.com". For a wildcard domain, use "*.example.com". Using a wildcard domain with subdomains requires a "DNS Provider" and the "DNS-01 Challenge" or a custom certificate.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>reverse.FromPort</id>
|
||||
|
||||
@@ -2,15 +2,20 @@
|
||||
<tab id="general-settings" description="General Settings">
|
||||
<field>
|
||||
<id>caddy.general.enabled</id>
|
||||
<label>Enabled</label>
|
||||
<label>Enable Caddy</label>
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable or disable the Caddy web server.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>caddy.general.EnableLayer4</id>
|
||||
<label>Enable Layer4 Proxy</label>
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable Layer4 Proxy to stream TCP/UDP. Layer4 Proxy matches before the Reverse Proxy; both can be used concurrently. More information: https://github.com/mholt/caddy-l4]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>caddy.general.TlsEmail</id>
|
||||
<label>ACME Email</label>
|
||||
<type>text</type>
|
||||
<hint>info@example.com</hint>
|
||||
<help><![CDATA[Enter the email address for certificate notifications. This is required to receive automatic certificates from "Let's Encrypt" and "ZeroSSL".]]></help>
|
||||
</field>
|
||||
<field>
|
||||
@@ -21,12 +26,6 @@
|
||||
</field>
|
||||
</tab>
|
||||
<tab id="general-advanced" description="Advanced Settings">
|
||||
<field>
|
||||
<id>caddy.general.EnableLayer4</id>
|
||||
<label>Enable Layer4</label>
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable Layer4 support to stream TCP/UDP. This feature is in active developement and the configuration can break or change in the future. The Layer4 app matches before the HTTP app. Deactivating this option will remove all Layer4 functionality completely. More information: https://github.com/mholt/caddy-l4]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>caddy.general.DisableSuperuser</id>
|
||||
<label>System User</label>
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
<pattern>api/caddy/reverse_proxy/*</pattern>
|
||||
</patterns>
|
||||
</page-caddy-reverse-proxy>
|
||||
<page-caddy-layer4-routes>
|
||||
<name>Services: Caddy Web Server: Layer4 Proxy</name>
|
||||
<description>Allow access to Caddy Layer4 Proxy</description>
|
||||
<patterns>
|
||||
<pattern>ui/caddy/layer4/*</pattern>
|
||||
<pattern>api/caddy/reverse_proxy/*</pattern>
|
||||
</patterns>
|
||||
</page-caddy-layer4-routes>
|
||||
<page-caddy-diagnostics>
|
||||
<name>Services: Caddy Web Server: Diagnostics</name>
|
||||
<description>Allow access to Caddy Diagnostics</description>
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
<Caddy VisibleName="Caddy Web Server" cssClass="fa fa-globe fa-fw">
|
||||
<General VisibleName="General Settings" order="10" url="/ui/caddy/general"/>
|
||||
<ReverseProxy VisibleName="Reverse Proxy" order="20" url="/ui/caddy/reverse_proxy"/>
|
||||
<Diagnostics VisibleName="Diagnostics" order="30" url="/ui/caddy/diagnostics"/>
|
||||
<Log VisibleName="Log File" order="40" url="/ui/diagnostics/log/core/caddy"/>
|
||||
<Layer4 VisibleName="Layer4 Proxy" order="30" url="/ui/caddy/layer4"/>
|
||||
<Diagnostics VisibleName="Diagnostics" order="40" url="/ui/caddy/diagnostics"/>
|
||||
<Log VisibleName="Log File" order="50" url="/ui/diagnostics/log/core/caddy"/>
|
||||
</Caddy>
|
||||
</Services>
|
||||
</menu>
|
||||
|
||||
@@ -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.
|
||||
#}
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Bootgrid Setup
|
||||
$("#Layer4Grid").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/',
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays an alert message to the user.
|
||||
*
|
||||
* @param {string} message - The message to display.
|
||||
* @param {string} [type="error"] - The type of alert (error or success).
|
||||
*/
|
||||
function showAlert(message, type = "error") {
|
||||
const alertClass = type === "error" ? "alert-danger" : "alert-success";
|
||||
const messageArea = $("#messageArea");
|
||||
|
||||
messageArea.stop(true, true).hide();
|
||||
messageArea.removeClass("alert-success alert-danger").addClass(alertClass).html(message);
|
||||
messageArea.fadeIn(500).delay(15000).fadeOut(500, function() {
|
||||
$(this).html('');
|
||||
});
|
||||
}
|
||||
|
||||
$('input, select, textarea').on('change', function() {
|
||||
$("#messageArea").hide();
|
||||
});
|
||||
|
||||
$("#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') {
|
||||
dfObj.resolve();
|
||||
} else {
|
||||
showAlert(data['message'], "error");
|
||||
dfObj.reject();
|
||||
}
|
||||
}).fail(function(xhr, status, error) {
|
||||
showAlert("{{ lang._('Validation request failed: ') }}" + error, "error");
|
||||
dfObj.reject();
|
||||
});
|
||||
|
||||
return dfObj.promise();
|
||||
},
|
||||
onAction: function(data, status) {
|
||||
if (status === "success" && data && data['status'].toLowerCase() === 'ok') {
|
||||
showAlert("{{ lang._('Configuration applied successfully.') }}", "{{ lang._('Apply Success') }}");
|
||||
updateServiceControlUI('caddy');
|
||||
} else {
|
||||
showAlert("{{ lang._('Action was not successful or an error occurred.') }}", "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("#layer4\\.Matchers").change(function() {
|
||||
if ($(this).val() !== "tlssni" && $(this).val() !== "httphost") {
|
||||
$(".style_matchers").closest('tr').hide();
|
||||
} else {
|
||||
$(".style_matchers").closest('tr').show();
|
||||
}
|
||||
});
|
||||
|
||||
updateServiceControlUI('caddy');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-header {
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li id="tab-layer4" class="active"><a data-toggle="tab" href="#layer4Tab">{{ lang._('Layer4 Routes') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box">
|
||||
<!-- Layer4 Tab -->
|
||||
<div id="layer4Tab" class="tab-pane fade active in">
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Layer4 Routes') }}</h1>
|
||||
<div style="display: block;"> <!-- Common container -->
|
||||
<table id="Layer4Grid" class="table table-condensed table-hover table-striped" data-editDialog="DialogLayer4" data-editAlert="ConfigurationChangeMessage">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
|
||||
<th data-column-id="enabled" data-width="6em" data-type="boolean" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
|
||||
<th data-column-id="Sequence" data-type="string">{{ lang._('Sequence') }}</th>
|
||||
<th data-column-id="Type" data-type="string" data-visible="false">{{ lang._('Routing Type') }}</th>
|
||||
<th data-column-id="Protocol" data-type="string">{{ lang._('Protocol') }}</th>
|
||||
<th data-column-id="FromPort" data-type="string" data-visible="false">{{ lang._('Local Port') }}</th>
|
||||
<th data-column-id="FromDomain" data-type="string">{{ lang._('Domain') }}</th>
|
||||
<th data-column-id="Matchers" data-type="string">{{ lang._('Matchers') }}</th>
|
||||
<th data-column-id="InvertMatchers" data-type="boolean" data-formatter="boolean" data-visible="false">{{ lang._('Invert Matchers') }}</th>
|
||||
<th data-column-id="ToDomain" data-type="string">{{ lang._('Upstream Domain') }}</th>
|
||||
<th data-column-id="ToPort" data-type="string">{{ lang._('Upstream Port') }}</th>
|
||||
<th data-column-id="RemoteIp" data-type="string" data-visible="false">{{ lang._('Remote IP') }}</th>
|
||||
<th data-column-id="PassiveHealthFailDuration" data-type="string" data-visible="false">{{ lang._('Fail Duration') }}</th>
|
||||
<th data-column-id="ProxyProtocol" data-type="string" data-visible="false">{{ lang._('Proxy Protocol') }}</th>
|
||||
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
|
||||
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button id="addLayer4Btn" data-action="add" type="button" class="btn btn-xs btn-primary"><span class="fa fa-plus"></span></button>
|
||||
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reconfigure Button -->
|
||||
<section class="page-content-main">
|
||||
<div class="content-box">
|
||||
<div class="col-md-12">
|
||||
<br/>
|
||||
<button class="btn btn-primary" id="reconfigureAct"
|
||||
data-endpoint="/api/caddy/service/reconfigure"
|
||||
data-label="{{ lang._('Apply') }}"
|
||||
data-error-title="{{ lang._('Error reconfiguring Caddy') }}"
|
||||
type="button"
|
||||
></button>
|
||||
<br/><br/>
|
||||
<!-- Message Area for error/success messages -->
|
||||
<div id="messageArea" class="alert alert-info" style="display: none;"></div>
|
||||
<!-- Message Area to hint user to apply changes when data is changed in bootgrids -->
|
||||
<div id="ConfigurationChangeMessage" class="alert alert-info" style="display: none;">
|
||||
{{ lang._('Please do not forget to apply the configuration.') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ partial("layout_partials/base_dialog",['fields':formDialogLayer4,'id':'DialogLayer4','label':lang._('Edit Layer4 Route')])}}
|
||||
@@ -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($('<option>').val(item.id).text(item.domainPort));
|
||||
@@ -141,91 +140,33 @@
|
||||
} else {
|
||||
select.html('<option value="">{{ lang._('Failed to load data') }}</option>');
|
||||
}
|
||||
select.selectpicker('refresh'); // Refresh selectpicker to update the UI
|
||||
select.selectpicker('refresh');
|
||||
}).fail(function() {
|
||||
$('#reverseFilter').html('<option value="">{{ lang._('Failed to load data') }}</option>').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();
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -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 @@
|
||||
</style>
|
||||
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li id="tab-layer4" style="display: none;"><a data-toggle="tab" href="#layer4Tab">{{ lang._('Layer4 Routes') }}</a></li>
|
||||
<li id="tab-domains" class="active"><a data-toggle="tab" href="#domainsTab">{{ lang._('Domains') }}</a></li>
|
||||
<li id="tab-subdomains" style="display: none;"><a data-toggle="tab" href="#subdomainsTab">{{ lang._('Subdomains') }}</a></li>
|
||||
<li id="tab-handlers"><a data-toggle="tab" href="#handlesTab">{{ lang._('HTTP Handlers') }}</a></li>
|
||||
<li id="tab-access"><a data-toggle="tab" href="#accessTab">{{ lang._('HTTP Access') }}</a></li>
|
||||
<li id="tab-headers"><a data-toggle="tab" href="#headerTab">{{ lang._('HTTP Headers') }}</a></li>
|
||||
@@ -387,13 +328,17 @@
|
||||
<button id="addDomainBtn" type="button" class="btn btn-secondary">{{ lang._('Step 1: Add Domain') }}</button>
|
||||
<button id="addHandleBtn" type="button" class="btn btn-secondary">{{ lang._('Step 2: Add HTTP Handler') }}</button>
|
||||
</div>
|
||||
<!-- Selectpicker on the right -->
|
||||
<select id="reverseFilter" class="selectpicker form-control" multiple data-live-search="true" data-width="348px" data-size="7" title="{{ lang._('Filter by Domain') }}">
|
||||
<!-- Options will be populated dynamically using JavaScript/Ajax -->
|
||||
</select>
|
||||
<!-- Selectpicker and Clear All on the right -->
|
||||
<div class="filter-actions" style="display: flex; flex-direction: column; align-items: flex-end;">
|
||||
<select id="reverseFilter" class="selectpicker form-control" multiple data-live-search="true" data-width="348px" data-size="7" title="{{ lang._('Filter by Domain') }}">
|
||||
</select>
|
||||
<a href="#" class="text-danger" id="clearDomains" style="margin-top: 5px;">
|
||||
<i class="fa fa-times-circle"></i> <small>Clear All</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reverse Proxy Tab -->
|
||||
<!-- Combined Domains Tab -->
|
||||
<div id="domainsTab" class="tab-pane fade in active">
|
||||
<div style="padding-left: 16px;">
|
||||
<!-- Reverse Proxy -->
|
||||
@@ -432,10 +377,8 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subdomains Tab -->
|
||||
<div id="subdomainsTab" class="tab-pane fade">
|
||||
<!-- Subdomains Tab -->
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Subdomains') }}</h1>
|
||||
<div style="display: block;"> <!-- Common container -->
|
||||
@@ -519,7 +462,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Combined Access Tab -->
|
||||
<!-- Combined Access Tab -->
|
||||
<div id="accessTab" class="tab-pane fade">
|
||||
<!-- Access Lists Section -->
|
||||
<div style="padding-left: 16px;">
|
||||
@@ -614,48 +557,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer4 Tab -->
|
||||
<div id="layer4Tab" class="tab-pane fade">
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Layer4 Routes') }}</h1>
|
||||
<div style="display: block;"> <!-- Common container -->
|
||||
<table id="reverseLayer4Grid" class="table table-condensed table-hover table-striped" data-editDialog="DialogLayer4" data-editAlert="ConfigurationChangeMessage">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
|
||||
<th data-column-id="enabled" data-width="6em" data-type="boolean" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
|
||||
<th data-column-id="Sequence" data-type="string">{{ lang._('Sequence') }}</th>
|
||||
<th data-column-id="Type" data-type="string" data-visible="false">{{ lang._('Routing Type') }}</th>
|
||||
<th data-column-id="Protocol" data-type="string">{{ lang._('Protocol') }}</th>
|
||||
<th data-column-id="FromPort" data-type="string" data-visible="false">{{ lang._('Local Port') }}</th>
|
||||
<th data-column-id="FromDomain" data-type="string">{{ lang._('Domain') }}</th>
|
||||
<th data-column-id="Matchers" data-type="string">{{ lang._('Matchers') }}</th>
|
||||
<th data-column-id="InvertMatchers" data-type="boolean" data-formatter="boolean" data-visible="false">{{ lang._('Invert Matchers') }}</th>
|
||||
<th data-column-id="ToDomain" data-type="string">{{ lang._('Upstream Domain') }}</th>
|
||||
<th data-column-id="ToPort" data-type="string">{{ lang._('Upstream Port') }}</th>
|
||||
<th data-column-id="RemoteIp" data-type="string" data-visible="false">{{ lang._('Remote IP') }}</th>
|
||||
<th data-column-id="PassiveHealthFailDuration" data-type="string" data-visible="false">{{ lang._('Fail Duration') }}</th>
|
||||
<th data-column-id="ProxyProtocol" data-type="string" data-visible="false">{{ lang._('Proxy Protocol') }}</th>
|
||||
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
|
||||
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button id="addReverseLayer4Btn" data-action="add" type="button" class="btn btn-xs btn-primary"><span class="fa fa-plus"></span></button>
|
||||
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reconfigure Button -->
|
||||
@@ -686,4 +587,3 @@
|
||||
{{ partial("layout_partials/base_dialog",['fields':formDialogAccessList,'id':'DialogAccessList','label':lang._('Edit Access List')])}}
|
||||
{{ partial("layout_partials/base_dialog",['fields':formDialogBasicAuth,'id':'DialogBasicAuth','label':lang._('Edit Basic Auth')])}}
|
||||
{{ partial("layout_partials/base_dialog",['fields':formDialogHeader,'id':'DialogHeader','label':lang._('Edit HTTP Header')])}}
|
||||
{{ partial("layout_partials/base_dialog",['fields':formDialogLayer4,'id':'DialogLayer4','label':lang._('Edit Layer4 Route')])}}
|
||||
|
||||
Reference in New Issue
Block a user