mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
www/caddy: Implement tabulator groupBy, fix tabulator 'Data Load Response Blocked' warning, remove unnecessary HTML (#4909)
* www/caddy: Implement tabulator groupBy into subdomain and handlers tabs, modernize style and html * www/caddy: Fix search endpoints being fired multiple times on initial page load, and when using the command buttons. This fixes some tabulator warnings and improves performance. * www/caddy: Bump version to 2.0.3 and add changelog
This commit is contained in:
+1
-2
@@ -1,6 +1,5 @@
|
||||
PLUGIN_NAME= caddy
|
||||
PLUGIN_VERSION= 2.0.2
|
||||
PLUGIN_REVISION= 2
|
||||
PLUGIN_VERSION= 2.0.3
|
||||
PLUGIN_DEPENDS= caddy-custom
|
||||
PLUGIN_COMMENT= Modern Reverse Proxy with Automatic HTTPS, Dynamic DNS and Layer4 Routing
|
||||
PLUGIN_MAINTAINER= cedrik@pischem.com
|
||||
|
||||
@@ -6,6 +6,12 @@ DOC: https://docs.opnsense.org/manual/how-tos/caddy.html
|
||||
Plugin Changelog
|
||||
================
|
||||
|
||||
2.0.3
|
||||
|
||||
Add: Tabulator groupBy of domain and subdomain (opnsense/plugins/pull/4909)
|
||||
Fix: Tabulator 'Data Load Response Blocked' warning
|
||||
Cleanup: Grid HTML and style
|
||||
|
||||
2.0.2
|
||||
|
||||
Add: Global server timeout options (opnsense/plugins/pull/4778)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable this handler.]]></help>
|
||||
<grid_view>
|
||||
<width>100</width>
|
||||
<width>20</width>
|
||||
<type>boolean</type>
|
||||
<formatter>rowtoggle</formatter>
|
||||
</grid_view>
|
||||
@@ -19,12 +19,18 @@
|
||||
<label>Domain</label>
|
||||
<type>dropdown</type>
|
||||
<help><![CDATA[Select a domain to handle.]]></help>
|
||||
<grid_view>
|
||||
<visible>false</visible>
|
||||
</grid_view>
|
||||
</field>
|
||||
<field>
|
||||
<id>handle.subdomain</id>
|
||||
<label>Subdomain</label>
|
||||
<type>dropdown</type>
|
||||
<help><![CDATA[Select a subdomain to handle. Make sure to additionaly choose a wildcard domain as "Domain". Leave unset, if not using subdomains.]]></help>
|
||||
<grid_view>
|
||||
<visible>false</visible>
|
||||
</grid_view>
|
||||
</field>
|
||||
<field>
|
||||
<type>header</type>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable this Layer4 route.]]></help>
|
||||
<grid_view>
|
||||
<width>100</width>
|
||||
<width>20</width>
|
||||
<type>boolean</type>
|
||||
<formatter>rowtoggle</formatter>
|
||||
</grid_view>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable this domain.]]></help>
|
||||
<grid_view>
|
||||
<width>100</width>
|
||||
<width>20</width>
|
||||
<type>boolean</type>
|
||||
<formatter>rowtoggle</formatter>
|
||||
</grid_view>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable this subdomain.]]></help>
|
||||
<grid_view>
|
||||
<width>100</width>
|
||||
<width>20</width>
|
||||
<type>boolean</type>
|
||||
<formatter>rowtoggle</formatter>
|
||||
</grid_view>
|
||||
@@ -27,7 +27,6 @@
|
||||
<id>subdomain.FromDomain</id>
|
||||
<label>Subdomain</label>
|
||||
<type>text</type>
|
||||
<hint>opn.example.com</hint>
|
||||
<help><![CDATA[Enter a subdomain. For example, "opn.example.com" if the wildcard domain is "*.example.com". All subdomains use the same ports and protocols as their parent wildcard domain.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
|
||||
@@ -26,11 +26,6 @@
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Update the URL hash when tabs are clicked
|
||||
if (location.hash) {
|
||||
$(`#maintabs a[href="${location.hash}"]`).tab('show');
|
||||
}
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
const hash = e.target.hash;
|
||||
if (history.replaceState) {
|
||||
@@ -41,12 +36,18 @@
|
||||
});
|
||||
|
||||
$(window).on('hashchange', function () {
|
||||
$(`#maintabs a[href="${location.hash}"]`).tab('show');
|
||||
const $targetTab = $(`#maintabs a[href="${location.hash}"]`);
|
||||
if ($targetTab.length) {
|
||||
$targetTab.tab('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Bootgrid Setup
|
||||
const all_grids = {};
|
||||
|
||||
// block change-driven grid reloads during programmatic updates of reverseFilter selectpicker
|
||||
let suppressFilterReload = false;
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let grid_ids = [];
|
||||
|
||||
@@ -96,17 +97,32 @@
|
||||
|
||||
if (["{{ formGridReverseProxy['table_id'] }}", "{{ formGridSubdomain['table_id'] }}"].includes(grid_id)) {
|
||||
const update_filter = function (selectValues) {
|
||||
$('#reverseFilter')
|
||||
// Refresh selectpicker with latest data so button always works even on new domains
|
||||
suppressFilterReload = true;
|
||||
|
||||
return $('#reverseFilter')
|
||||
.fetch_options('/api/caddy/reverse_proxy/get_all_reverse_domains')
|
||||
.done(function () {
|
||||
$('#reverseFilter')
|
||||
.selectpicker('val', selectValues)
|
||||
.selectpicker('refresh')
|
||||
.trigger('change');
|
||||
});
|
||||
.selectpicker('refresh');
|
||||
|
||||
$('#maintabs a[href="#handlers"]').tab('show');
|
||||
// manually update icon (since we skip .change())
|
||||
const hasSelection = Array.isArray(selectValues) && selectValues.length > 0;
|
||||
$('#reverseFilterIcon')
|
||||
.toggleClass('text-success fa-filter-circle-xmark', hasSelection)
|
||||
.toggleClass('fa-filter', !hasSelection);
|
||||
|
||||
$('#maintabs a[href="#handlers"]').tab('show');
|
||||
|
||||
// manually reload just the handlers grid
|
||||
const grid_id = "{{ formGridHandle['table_id'] }}";
|
||||
if (all_grids[grid_id]) {
|
||||
all_grids[grid_id].bootgrid('reload');
|
||||
}
|
||||
})
|
||||
.always(function () {
|
||||
suppressFilterReload = false;
|
||||
});
|
||||
};
|
||||
|
||||
commands.search_handler = {
|
||||
@@ -132,7 +148,6 @@
|
||||
// Ensure selectpicker has values selected before click on add button
|
||||
$('#reverseFilter').one('changed.bs.select', function (e) {
|
||||
$("#" + "{{ formGridHandle['table_id'] }}")
|
||||
.closest('.bootgrid-box')
|
||||
.find("button[data-action='add']")
|
||||
.trigger('click');
|
||||
});
|
||||
@@ -157,6 +172,7 @@
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
|
||||
all_grids[grid_id] = $("#" + grid_id)
|
||||
.UIBootgrid({
|
||||
search: `/api/caddy/reverse_proxy/search_${grid_id}/`,
|
||||
@@ -165,7 +181,35 @@
|
||||
add: `/api/caddy/reverse_proxy/add_${grid_id}/`,
|
||||
del: `/api/caddy/reverse_proxy/del_${grid_id}/`,
|
||||
toggle: `/api/caddy/reverse_proxy/toggle_${grid_id}/`,
|
||||
|
||||
{% if entrypoint == 'reverse_proxy' %}
|
||||
|
||||
tabulatorOptions: {
|
||||
groupBy: grid_id === "{{ formGridHandle['table_id'] }}"
|
||||
? ["%reverse", "%subdomain"]
|
||||
: (grid_id === "{{ formGridSubdomain['table_id'] }}" ? "%reverse" : false),
|
||||
groupHeader: (value, count, data, group) => {
|
||||
const isNested = group.getParentGroup() !== false;
|
||||
|
||||
if (isNested && !value) {
|
||||
return ''; // remove empty subdomain group headers
|
||||
}
|
||||
|
||||
const iconClass = isNested
|
||||
? 'text-warning'
|
||||
: 'text-success';
|
||||
|
||||
const countValue = `<span class="badge chip">${count}</span>`;
|
||||
|
||||
return `<i class="fa fa-fw fa-globe ${iconClass}"></i> ${value} ${countValue}`;
|
||||
},
|
||||
},
|
||||
|
||||
{% endif %}
|
||||
|
||||
options: {
|
||||
responsive: true,
|
||||
rowCount: [20,50,200,500,1000,-1],
|
||||
requestHandler: function (request) {
|
||||
const selectedDomains = $('#reverseFilter').val();
|
||||
if (selectedDomains && selectedDomains.length > 0) {
|
||||
@@ -174,6 +218,9 @@
|
||||
return request;
|
||||
},
|
||||
headerFormatters: {
|
||||
enabled: function (column) {
|
||||
return '<i class="fa-solid fa-fw fa-check-square" data-toggle="tooltip" title="{{ lang._('Enabled') }}"></i>';;
|
||||
},
|
||||
ToDomain: function (column) { return labels.upstream; },
|
||||
FromDomain: function (column) {
|
||||
if (grid_id === "subdomain") {
|
||||
@@ -209,9 +256,6 @@
|
||||
},
|
||||
commands: commands
|
||||
});
|
||||
|
||||
$("#" + grid_id).wrap('<div class="bootgrid-box"></div>');
|
||||
|
||||
}
|
||||
|
||||
{% if entrypoint == 'reverse_proxy' %}
|
||||
@@ -221,8 +265,7 @@
|
||||
const header = $("#" + grid_id + "-header");
|
||||
const $actionBar = header.find('.actionBar');
|
||||
if ($actionBar.length) {
|
||||
$('#add_filter_container').detach().insertBefore($actionBar.find('.search'));
|
||||
$('#add_filter_container').show();
|
||||
$('#add_filter_container').detach().insertBefore($actionBar.find('.search')).show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +332,7 @@
|
||||
|
||||
// Safe reload on filter change, ensures all grids are initalized beforehand
|
||||
$('#reverseFilter').change(function () {
|
||||
if (suppressFilterReload) return;
|
||||
Object.keys(all_grids).forEach(function (grid_id) {
|
||||
if ([
|
||||
'{{ formGridReverseProxy["table_id"] }}',
|
||||
@@ -386,61 +430,38 @@
|
||||
$("#handle\\.reverse").selectpicker("refresh");
|
||||
});
|
||||
|
||||
// Trigger bootgrid setup for handlers tab too (even if not active) to ensure command buttons always work
|
||||
$('a[href="#handlers"]').trigger('shown.bs.tab');
|
||||
// Trigger initial tab
|
||||
const $tabs = $('#maintabs a[data-toggle="tab"]');
|
||||
const $initial = $tabs.filter(`[href="${location.hash}"]`).first();
|
||||
($initial.length ? $initial : $tabs.first()).tab('show');
|
||||
|
||||
// Trigger handlers tab too (even if not active) to ensure command buttons always work
|
||||
$(document).one('ajaxStop', function () {
|
||||
$('a[href="#handlers"]').triggerHandler('shown.bs.tab');
|
||||
});
|
||||
|
||||
updateServiceControlUI('caddy');
|
||||
$('<div id="messageArea" class="alert alert-info" style="display: none;"></div>').insertBefore('#change_message_base_form');
|
||||
$('a[data-toggle="tab"].active, #maintabs li.active a').trigger('shown.bs.tab');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#add_filter_container {
|
||||
margin-left: 10px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
#add_domain_container {
|
||||
float: left;
|
||||
}
|
||||
#add_handle_container {
|
||||
margin-left: 10px;
|
||||
float: left;
|
||||
}
|
||||
.actionBar {
|
||||
padding-left: 0px;
|
||||
}
|
||||
.custom-header {
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
/* Prevent bootgrid to break out of content box*/
|
||||
.content-box {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.bootgrid-header,
|
||||
.bootgrid-box,
|
||||
.bootgrid-footer {
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
max-width: 100%;
|
||||
/* Prevents the grid from collapsing all dynamic columns completely */
|
||||
min-width: 700px;
|
||||
}
|
||||
/* Not all dropdowns support data-container="body", ensure minimal vertical space for them */
|
||||
.bootgrid-box {
|
||||
min-height: 150px;
|
||||
}
|
||||
/* Limit size of grid dropdown */
|
||||
.actions .dropdown-menu.pull-right {
|
||||
max-height: 200px;
|
||||
max-height: 400px;
|
||||
min-width: max-content;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.custom-header {
|
||||
padding-left: 20px;
|
||||
font-weight: 800;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
#reverseFilterClear {
|
||||
border-right: none;
|
||||
}
|
||||
@@ -454,7 +475,7 @@
|
||||
<button type="button" id="reverseFilterClear" class="btn btn-default" title="{{ lang._('Clear Selection') }}">
|
||||
<i id="reverseFilterIcon" class="fa fa-fw fa-filter"></i>
|
||||
</button>
|
||||
<select id="reverseFilter" class="selectpicker form-control" multiple data-live-search="true" data-width="200px" data-size="10" data-container="body" title="{{ lang._('Filter by Domain') }}">
|
||||
<select id="reverseFilter" class="selectpicker form-control" multiple data-live-search="true" data-width="300px" data-size="10" data-container="body" title="{{ lang._('Filter by Domain') }}">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -462,14 +483,14 @@
|
||||
|
||||
{% if entrypoint == 'reverse_proxy' %}
|
||||
|
||||
<li id="tab-domains" class="active"><a data-toggle="tab" href="#domains">{{ lang._('Domains') }}</a></li>
|
||||
<li id="tab-domains"><a data-toggle="tab" href="#domains">{{ lang._('Domains') }}</a></li>
|
||||
<li id="tab-handlers"><a data-toggle="tab" href="#handlers">{{ lang._('Handlers') }}</a></li>
|
||||
<li id="tab-access"><a data-toggle="tab" href="#access">{{ lang._('Access') }}</a></li>
|
||||
<li id="tab-headers"><a data-toggle="tab" href="#headers">{{ lang._('Headers') }}</a></li>
|
||||
|
||||
{% elseif entrypoint == 'layer4' %}
|
||||
|
||||
<li id="tab-layer4" class="active"><a data-toggle="tab" href="#routes">{{ lang._('Layer4 Routes') }}</a></li>
|
||||
<li id="tab-layer4"><a data-toggle="tab" href="#routes">{{ lang._('Layer4 Routes') }}</a></li>
|
||||
<li id="tab-matcher"><a data-toggle="tab" href="#matchers">{{ lang._('Layer7 Matcher Settings') }}</a></li>
|
||||
|
||||
{% endif %}
|
||||
@@ -482,83 +503,50 @@
|
||||
|
||||
<!-- Combined Domains Tab -->
|
||||
<div id="domains" class="tab-pane fade in active">
|
||||
<div style="padding-left: 16px;">
|
||||
<!-- Reverse Proxy -->
|
||||
<h1 class="custom-header">{{ lang._('Domains') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridReverseProxy + {'command_width': '160'})}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Reverse Proxy -->
|
||||
<h1 class="custom-header">{{ lang._('Domains') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridReverseProxy + {'command_width': '160'})}}
|
||||
|
||||
<!-- Subdomains Tab -->
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Subdomains') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridSubdomain + {'command_width': '160'})}}
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="custom-header">{{ lang._('Subdomains') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridSubdomain + {'command_width': '160'})}}
|
||||
</div>
|
||||
|
||||
<!-- Handle Tab -->
|
||||
<div id="handlers" class="tab-pane fade">
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Handlers') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridHandle)}}
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="custom-header">{{ lang._('Handlers') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridHandle)}}
|
||||
</div>
|
||||
|
||||
<!-- Combined Access Tab -->
|
||||
<div id="access" class="tab-pane fade">
|
||||
<!-- Access Lists Section -->
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Access Lists') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridAccessList)}}
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="custom-header">{{ lang._('Access Lists') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridAccessList)}}
|
||||
|
||||
<!-- Basic Auth Section -->
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Basic Auth') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridBasicAuth)}}
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="custom-header">{{ lang._('Basic Auth') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridBasicAuth)}}
|
||||
</div>
|
||||
|
||||
<!-- Header Tab -->
|
||||
<div id="headers" class="tab-pane fade">
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Headers') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridHeader)}}
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="custom-header">{{ lang._('Headers') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridHeader)}}
|
||||
</div>
|
||||
|
||||
{% elseif entrypoint == 'layer4' %}
|
||||
|
||||
<!-- Layer4 Tab -->
|
||||
<div id="routes" class="tab-pane fade active in">
|
||||
<div style="padding-left: 16px;">
|
||||
<h1 class="custom-header">{{ lang._('Layer4 Routes') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridLayer4)}}
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="custom-header">{{ lang._('Layer4 Routes') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridLayer4)}}
|
||||
</div>
|
||||
|
||||
<!-- Layer7 Tab -->
|
||||
<div id="matchers" class="tab-pane fade">
|
||||
<div style="padding-left: 16px;">
|
||||
<!-- OpenVPN Matcher -->
|
||||
<h1 class="custom-header">{{ lang._('OpenVPN Static Keys') }}</h1>
|
||||
<div style="display: block;">
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridLayer4Openvpn)}}
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="custom-header">{{ lang._('OpenVPN Static Keys') }}</h1>
|
||||
{{ partial('layout_partials/base_bootgrid_table', formGridLayer4Openvpn)}}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user