From b9dcae8a9c9e10a27f265dff5b0d4334e2903ccb Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Sun, 8 Feb 2026 00:08:08 +0100 Subject: [PATCH 01/87] net/haproxy: support mapfiles in hdr/path ACLs Previously a path or header had to be specified. But with the extended mapfile support, these are no longer required values. A mapfile may be used instead. --- .../templates/OPNsense/HAProxy/haproxy.conf | 121 +++++++----------- 1 file changed, 44 insertions(+), 77 deletions(-) diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf index 21b83addf..7e9c221cf 100644 --- a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf @@ -171,59 +171,44 @@ # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'hdr' %} +{% do acl_options.append('hdr(host)') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.hdr|default("") != "" %} -{% do acl_options.append('hdr(host)') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.hdr) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'hdr_beg' %} +{% do acl_options.append('hdr_beg(host)') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.hdr_beg|default("") != "" %} -{% do acl_options.append('hdr_beg(host)') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.hdr_beg) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'hdr_end' %} +{% do acl_options.append('hdr_end(host)') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.hdr_end|default("") != "" %} -{% do acl_options.append('hdr_end(host)') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.hdr_end) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'hdr_reg' %} +{% do acl_options.append('hdr_reg(host)') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.hdr_reg|default("") != "" %} -{% do acl_options.append('hdr_reg(host)') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.hdr_reg) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'hdr_sub' %} +{% do acl_options.append('hdr_sub(host)') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.hdr_sub|default("") != "" %} -{% do acl_options.append('hdr_sub(host)') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.hdr_sub) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'http_auth' %} {% if acl_data.allowedUsers|default("") != "" or acl_data.allowedGroups|default("") != "" %} @@ -253,70 +238,52 @@ # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'path' %} +{% do acl_options.append('path') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.path|default("") != "" %} -{% do acl_options.append('path') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.path) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'path_beg' %} +{% do acl_options.append('path_beg') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.path_beg|default("") != "" %} -{% do acl_options.append('path_beg') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.path_beg) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'path_dir' %} +{% do acl_options.append('path_dir') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.path_dur|default("") != "" %} -{% do acl_options.append('path_dir') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.path_dir) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'path_end' %} +{% do acl_options.append('path_end') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.path_end|default("") != "" %} -{% do acl_options.append('path_end') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.path_end) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'path_reg' %} +{% do acl_options.append('path_reg') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.path_reg|default("") != "" %} -{% do acl_options.append('path_reg') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.path_reg) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'path_sub' %} +{% do acl_options.append('path_sub') %} +{% if acl_data.caseSensitive|default('0') == '0' %} +{% do acl_options.append('-i') %} +{% endif %} {% if acl_data.path_sub|default("") != "" %} -{% do acl_options.append('path_sub') %} -{% if acl_data.caseSensitive|default('0') == '0' %} -{% do acl_options.append('-i') %} -{% endif %} {% do acl_options.append(acl_data.path_sub) %} -{% else %} -{% set acl_enabled = '0' %} - # ERROR: missing parameters {% endif %} {% elif acl_data.expression == 'sc_bytes_in_rate' %} {% if acl_data.sc_number|default("") != "" and acl_data.sc_bytes_in_rate|default("") != "" %} From 3c2dd310fedd22c6d16937dbd60c7acaad4368cf Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Sun, 8 Feb 2026 22:43:34 +0100 Subject: [PATCH 02/87] net/haproxy: support more advanced sample fetches and converters --- net/haproxy/pkg-descr | 3 +- .../OPNsense/HAProxy/forms/dialogAcl.xml | 27 ++++++++++++ .../OPNsense/HAProxy/forms/dialogAction.xml | 18 ++++++++ .../app/models/OPNsense/HAProxy/HAProxy.xml | 43 +++++++++++++++++++ .../templates/OPNsense/HAProxy/haproxy.conf | 28 ++++++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) diff --git a/net/haproxy/pkg-descr b/net/haproxy/pkg-descr index c0396f6dc..0af36b5e2 100644 --- a/net/haproxy/pkg-descr +++ b/net/haproxy/pkg-descr @@ -27,7 +27,8 @@ Added: * add support for GPC/GPT/SC to conditions and rules (#1123, #5109) * add support for SSL SNI expression to servers (#3756) * add column "mode" to servers overview (#4632) -* add support for loading mapfiles in conditions +* add support for loading mapfiles in conditions and rules +* add support for sample fetches in rules Fixed: * Maintenance tab "SSL Certificates" not working with only one cert diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml index 39716d8ad..192978937 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAcl.xml @@ -266,6 +266,27 @@ text + + + header + + + + acl.var_comparison + + dropdown + + + acl.var + + text + + + + acl.var_value + + text + header @@ -1788,4 +1809,10 @@ dropdown + + acl.converter + + text + + diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml index 0222a2f4b..64c4230b3 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogAction.xml @@ -325,4 +325,22 @@ text + + action.mapfile + + dropdown + + + + action.map_default + + text + + + + action.sample_fetch + + text + http-request set-var(req.rate_limit) path,map_beg(/path/to/mapfile,20)
http-request set-var(req.request_rate) base32+src,table_http_req_rate()]]>
+
diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index a99d6d469..17d19969a 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -1951,6 +1951,7 @@ ssl_sni_sub – SNI TLS extension contains (TCP request content inspection) stopping – HAProxy process is currently stopping url_param – URL parameter contains + var – Compare the value of a variable wait_end – Inspection period is over Custom condition (option pass-through) @@ -2076,6 +2077,25 @@ /^.{1,4096}$/u N + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + + + N + gt + + greater than + greater equal + equal + less than + less equal + + 0 500000 @@ -3467,6 +3487,10 @@ Related mapfile item not found N + + /^.{1,4096}$/u + N + @@ -4142,6 +4166,25 @@ Please specify a value between 0 and 99. N + + + + + Related mapfile item not found + N + + + /^.{1,4096}$/u + N + + + /^.{1,4096}$/u + N + diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf index 7e9c221cf..bf0ad72d8 100644 --- a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf @@ -1381,6 +1381,18 @@ {% set acl_enabled = '0' %} # ERROR: missing parameters {% endif %} +{% elif acl_data.expression == 'var' %} +{% if acl_data.var|default("") != "" and acl_data.var_value|default("") != "" %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('var' ~ acl_data.var ~ converter_data ~ ' ' ~ acl_data.var_comparison ~ ' ' ~ acl_data.var_value) %} +{% else %} +{% set acl_enabled = '0' %} + # ERROR: missing parameters +{% endif %} {# # handle boolean ACL types that do not require any input #} {% elif acl_data.expression in acl_boolean_types %} {% do acl_options.append(acl_data.expression) %} @@ -1637,6 +1649,22 @@ {% set action_enabled = '0' %} {% do global_action_options.append('# ERROR: unsupported rule type ' ~ action_data.type) %} {% endif %} +{# # Add sample fetch to map file config. #} +{% if action_data.mapfile|default("") != "" %} +{% set mapfile_data = helpers.getUUID(action_data.mapfile) %} +{% set mapfile_path = '/tmp/haproxy/mapfiles/' ~ mapfile_data.id ~ '.txt' %} +{% set mapfile_config = 'map_' ~ mapfile_data.type %} +{% if action_data.map_default|default("") != "" %} +{% set mapfile_default = ',' ~ action_data.map_default %} +{% endif %} +{% if action_data.sample_fetch|default("") != "" %} +{% set mapfile_sf = action_data.sample_fetch ~ ',' %} +{% endif %} +{% do action_options.append(mapfile_sf ~ mapfile_config ~ '(' ~ mapfile_path ~ mapfile_default ~ ')') %} +{# # Add/append sample fetch. #} +{% elif action_data.sample_fetch|default("") != "" %} +{% do action_options.append(action_data.sample_fetch) %} +{% endif %} {# # Is this rule enabled in the GUI? #} {% if action_data.enabled|default('') == '1' %} {# # check if action is valid #} From acbaa92aad8fcb5deedffb2baaec659df6a8a3f3 Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Mon, 9 Feb 2026 07:42:21 +0100 Subject: [PATCH 03/87] net/haproxy: style sweep and LICENSE sync --- .../app/models/OPNsense/HAProxy/HAProxy.xml | 272 +++++++++--------- .../OPNsense/HAProxy/Migrations/M5_0_0.php | 2 +- 2 files changed, 137 insertions(+), 137 deletions(-) diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index 17d19969a..fb4a4b8e0 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -1825,134 +1825,134 @@ Y - hdr_beg – specified HTTP Header starts with - hdr_end – specified HTTP Header ends with - hdr – specified HTTP Header matches - hdr_reg – specified HTTP Header regex - hdr_sub – specified HTTP Header contains - hdr_beg – HTTP Host Header starts with - hdr_end – HTTP Host Header ends with - hdr – HTTP Host Header matches - hdr_reg – HTTP Host Header regex - hdr_sub – HTTP Host Header contains - http_auth – HTTP Basic Auth: username/password from client matches selected User/Group - http_method – HTTP Method - nbsrv – Minimum number of usable servers in backend - path_beg – Path starts with - path_dir – Path contains subdir - path_end – Path ends with - path – Path matches - path_reg – Path regex - path_sub – Path contains string - quic_enabled – QUIC transport protocol is enabled - req.proto_http – Traffic is HTTP - req.ssl_ver – Traffic is SSL (TCP request content inspection) - sc_bytes_in_rate – Sticky counter: incoming bytes rate - sc_bytes_out_rate – Sticky counter: outgoing bytes rate - sc_clr_gpc – Sticky counter: clear General Purpose Counter - sc_clr_gpc0 – Sticky counter: clear General Purpose Counter - sc_clr_gpc1 – Sticky counter: clear General Purpose Counter - sc0_clr_gpc0 – Sticky counter: clear General Purpose Counter - sc0_clr_gpc1 – Sticky counter: clear General Purpose Counter - sc1_clr_gpc – Sticky counter: clear General Purpose Counter - sc1_clr_gpc0 – Sticky counter: clear General Purpose Counter - sc1_clr_gpc1 – Sticky counter: clear General Purpose Counter - sc2_clr_gpc – Sticky counter: clear General Purpose Counter - sc2_clr_gpc0 – Sticky counter: clear General Purpose Counter - sc2_clr_gpc1 – Sticky counter: clear General Purpose Counter - sc_conn_cnt – Sticky counter: cumulative number of connections - sc_conn_cur – Sticky counter: concurrent connections - sc_conn_rate – Sticky counter: connection rate - sc_get_gpc – Sticky counter: get General Purpose Counter value - sc_get_gpc0 – Sticky counter: get General Purpose Counter value - sc_get_gpc1 – Sticky counter: get General Purpose Counter value - sc0_get_gpc0 – Sticky counter: get General Purpose Counter value - sc0_get_gpc1 – Sticky counter: get General Purpose Counter value - sc1_get_gpc0 – Sticky counter: get General Purpose Counter value - sc1_get_gpc1 – Sticky counter: get General Purpose Counter value - sc2_get_gpc0 – Sticky counter: get General Purpose Counter value - sc2_get_gpc1 – Sticky counter: get General Purpose Counter value - sc_get_gpt – Sticky counter: get General Purpose Tag value - sc_get_gpt0 – Sticky counter: get General Purpose Tag value - sc0_get_gpt0 – Sticky counter: get General Purpose Tag value - sc1_get_gpt0 – Sticky counter: get General Purpose Tag value - sc2_get_gpt0 – Sticky counter: get General Purpose Tag value - sc_glitch_cnt – Sticky counter: cumulative number of glitches - sc_glitch_rate – Sticky counter: rate of glitches - sc_gpc_rate – Sticky counter: increment rate of General Purpose Counter - sc_gpc0_rate – Sticky counter: increment rate of General Purpose Counter - sc_gpc1_rate – Sticky counter: increment rate of General Purpose Counter - sc0_gpc0_rate – Sticky counter: increment rate of General Purpose Counter - sc0_gpc1_rate – Sticky counter: increment rate of General Purpose Counter - sc1_gpc0_rate – Sticky counter: increment rate of General Purpose Counter - sc1_gpc1_rate – Sticky counter: increment rate of General Purpose Counter - sc2_gpc0_rate – Sticky counter: increment rate of General Purpose Counter - sc2_gpc1_rate – Sticky counter: increment rate of General Purpose Counter - sc_http_err_cnt – Sticky counter: cumulative number of HTTP errors - sc_http_err_rate – Sticky counter: rate of HTTP errors - sc_http_fail_cnt – Sticky counter: cumulative number of HTTP failures - sc_http_fail_rate – Sticky counter: rate of HTTP failures - sc_http_req_cnt – Sticky counter: cumulative number of HTTP requests - sc_http_req_rate – Sticky counter: rate of HTTP requests - sc_inc_gpc – Sticky counter: increment General Purpose Counter - sc_inc_gpc0 – Sticky counter: increment General Purpose Counter - sc_inc_gpc1 – Sticky counter: increment General Purpose Counter - sc0_inc_gpc0 – Sticky counter: increment General Purpose Counter - sc0_inc_gpc1 – Sticky counter: increment General Purpose Counter - sc1_inc_gpc0 – Sticky counter: increment General Purpose Counter - sc1_inc_gpc1 – Sticky counter: increment General Purpose Counter - sc2_inc_gpc0 – Sticky counter: increment General Purpose Counter - sc2_inc_gpc1 – Sticky counter: increment General Purpose Counter - sc_sess_cnt – Sticky counter: cumulative number of sessions - sc_sess_rate – Sticky counter: session rate - src – Source IP matches specified IP - src_bytes_in_rate – Source IP: incoming bytes rate - src_bytes_out_rate – Source IP: outgoing bytes rate - src_clr_gpc – Source IP: clear General Purpose Counter - src_clr_gpc0 – Source IP: clear General Purpose Counter - src_clr_gpc1 – Source IP: clear General Purpose Counter - src_conn_cnt – Source IP: cumulative number of connections - src_conn_cur – Source IP: concurrent connections - src_conn_rate – Source IP: connection rate - src_get_gpc – Source IP: get General Purpose Counter value - src_get_gpc0 – Source IP: get General Purpose Counter value - src_get_gpc1 – Source IP: get General Purpose Counter value - src_get_gpt – Source IP: get General Purpose Tag value - src_glitch_cnt – Source IP: cumulative number of glitches - src_glitch_rate – Source IP: rate of glitches - src_gpc_rate – Source IP: increment rate of General Purpose Counter - src_gpc0_rate – Source IP: increment rate of General Purpose Counter - src_gpc1_rate – Source IP: increment rate of General Purpose Counter - src_http_err_cnt – Source IP: cumulative number of HTTP errors - src_http_err_rate – Source IP: rate of HTTP errors - src_http_fail_cnt – Source IP: cumulative number of HTTP failures - src_http_fail_rate – Source IP: rate of HTTP failures - src_http_req_cnt – Source IP: number of HTTP requests - src_http_req_rate – Source IP: rate of HTTP requests - src_inc_gpc – Source IP: increment General Purpose Counter - src_inc_gpc0 – Source IP: increment General Purpose Counter - src_inc_gpc1 – Source IP: increment General Purpose Counter - src_is_local – Source IP is local - src_kbytes_in – Source IP: amount of data received (in kilobytes) - src_kbytes_out – Source IP: amount of data sent (in kilobytes) - src_port – Source IP: TCP source port - src_sess_cnt – Source IP: cumulative number of sessions - src_sess_rate – Source IP: session rate - ssl_c_ca_commonname – SSL Client certificate issued by CA common-name - ssl_c_verify_code – SSL Client certificate verify error result - ssl_c_verify – SSL Client certificate is valid - ssl_fc_sni – SNI TLS extension matches (locally deciphered) - ssl_fc – Traffic is SSL (locally deciphered) - ssl_hello_type – SSL Hello Type - ssl_sni_beg – SNI TLS extension starts with (TCP request content inspection) - ssl_sni_end – SNI TLS extension ends with (TCP request content inspection) - ssl_sni_reg – SNI TLS extension regex (TCP request content inspection) - ssl_sni – SNI TLS extension matches (TCP request content inspection) - ssl_sni_sub – SNI TLS extension contains (TCP request content inspection) - stopping – HAProxy process is currently stopping - url_param – URL parameter contains - var – Compare the value of a variable - wait_end – Inspection period is over + hdr_beg - specified HTTP Header starts with + hdr_end - specified HTTP Header ends with + hdr - specified HTTP Header matches + hdr_reg - specified HTTP Header regex + hdr_sub - specified HTTP Header contains + hdr_beg - HTTP Host Header starts with + hdr_end - HTTP Host Header ends with + hdr - HTTP Host Header matches + hdr_reg - HTTP Host Header regex + hdr_sub - HTTP Host Header contains + http_auth - HTTP Basic Auth: username/password from client matches selected User/Group + http_method - HTTP Method + nbsrv - Minimum number of usable servers in backend + path_beg - Path starts with + path_dir - Path contains subdir + path_end - Path ends with + path - Path matches + path_reg - Path regex + path_sub - Path contains string + quic_enabled - QUIC transport protocol is enabled + req.proto_http - Traffic is HTTP + req.ssl_ver - Traffic is SSL (TCP request content inspection) + sc_bytes_in_rate - Sticky counter: incoming bytes rate + sc_bytes_out_rate - Sticky counter: outgoing bytes rate + sc_clr_gpc - Sticky counter: clear General Purpose Counter + sc_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc0_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc0_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc1_clr_gpc - Sticky counter: clear General Purpose Counter + sc1_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc1_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc2_clr_gpc - Sticky counter: clear General Purpose Counter + sc2_clr_gpc0 - Sticky counter: clear General Purpose Counter + sc2_clr_gpc1 - Sticky counter: clear General Purpose Counter + sc_conn_cnt - Sticky counter: cumulative number of connections + sc_conn_cur - Sticky counter: concurrent connections + sc_conn_rate - Sticky counter: connection rate + sc_get_gpc - Sticky counter: get General Purpose Counter value + sc_get_gpc0 - Sticky counter: get General Purpose Counter value + sc_get_gpc1 - Sticky counter: get General Purpose Counter value + sc0_get_gpc0 - Sticky counter: get General Purpose Counter value + sc0_get_gpc1 - Sticky counter: get General Purpose Counter value + sc1_get_gpc0 - Sticky counter: get General Purpose Counter value + sc1_get_gpc1 - Sticky counter: get General Purpose Counter value + sc2_get_gpc0 - Sticky counter: get General Purpose Counter value + sc2_get_gpc1 - Sticky counter: get General Purpose Counter value + sc_get_gpt - Sticky counter: get General Purpose Tag value + sc_get_gpt0 - Sticky counter: get General Purpose Tag value + sc0_get_gpt0 - Sticky counter: get General Purpose Tag value + sc1_get_gpt0 - Sticky counter: get General Purpose Tag value + sc2_get_gpt0 - Sticky counter: get General Purpose Tag value + sc_glitch_cnt - Sticky counter: cumulative number of glitches + sc_glitch_rate - Sticky counter: rate of glitches + sc_gpc_rate - Sticky counter: increment rate of General Purpose Counter + sc_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc0_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc0_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc1_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc1_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc2_gpc0_rate - Sticky counter: increment rate of General Purpose Counter + sc2_gpc1_rate - Sticky counter: increment rate of General Purpose Counter + sc_http_err_cnt - Sticky counter: cumulative number of HTTP errors + sc_http_err_rate - Sticky counter: rate of HTTP errors + sc_http_fail_cnt - Sticky counter: cumulative number of HTTP failures + sc_http_fail_rate - Sticky counter: rate of HTTP failures + sc_http_req_cnt - Sticky counter: cumulative number of HTTP requests + sc_http_req_rate - Sticky counter: rate of HTTP requests + sc_inc_gpc - Sticky counter: increment General Purpose Counter + sc_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc0_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc0_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc1_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc1_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc2_inc_gpc0 - Sticky counter: increment General Purpose Counter + sc2_inc_gpc1 - Sticky counter: increment General Purpose Counter + sc_sess_cnt - Sticky counter: cumulative number of sessions + sc_sess_rate - Sticky counter: session rate + src - Source IP matches specified IP + src_bytes_in_rate - Source IP: incoming bytes rate + src_bytes_out_rate - Source IP: outgoing bytes rate + src_clr_gpc - Source IP: clear General Purpose Counter + src_clr_gpc0 - Source IP: clear General Purpose Counter + src_clr_gpc1 - Source IP: clear General Purpose Counter + src_conn_cnt - Source IP: cumulative number of connections + src_conn_cur - Source IP: concurrent connections + src_conn_rate - Source IP: connection rate + src_get_gpc - Source IP: get General Purpose Counter value + src_get_gpc0 - Source IP: get General Purpose Counter value + src_get_gpc1 - Source IP: get General Purpose Counter value + src_get_gpt - Source IP: get General Purpose Tag value + src_glitch_cnt - Source IP: cumulative number of glitches + src_glitch_rate - Source IP: rate of glitches + src_gpc_rate - Source IP: increment rate of General Purpose Counter + src_gpc0_rate - Source IP: increment rate of General Purpose Counter + src_gpc1_rate - Source IP: increment rate of General Purpose Counter + src_http_err_cnt - Source IP: cumulative number of HTTP errors + src_http_err_rate - Source IP: rate of HTTP errors + src_http_fail_cnt - Source IP: cumulative number of HTTP failures + src_http_fail_rate - Source IP: rate of HTTP failures + src_http_req_cnt - Source IP: number of HTTP requests + src_http_req_rate - Source IP: rate of HTTP requests + src_inc_gpc - Source IP: increment General Purpose Counter + src_inc_gpc0 - Source IP: increment General Purpose Counter + src_inc_gpc1 - Source IP: increment General Purpose Counter + src_is_local - Source IP is local + src_kbytes_in - Source IP: amount of data received (in kilobytes) + src_kbytes_out - Source IP: amount of data sent (in kilobytes) + src_port - Source IP: TCP source port + src_sess_cnt - Source IP: cumulative number of sessions + src_sess_rate - Source IP: session rate + ssl_c_ca_commonname - SSL Client certificate issued by CA common-name + ssl_c_verify_code - SSL Client certificate verify error result + ssl_c_verify - SSL Client certificate is valid + ssl_fc_sni - SNI TLS extension matches (locally deciphered) + ssl_fc - Traffic is SSL (locally deciphered) + ssl_hello_type - SSL Hello Type + ssl_sni_beg - SNI TLS extension starts with (TCP request content inspection) + ssl_sni_end - SNI TLS extension ends with (TCP request content inspection) + ssl_sni_reg - SNI TLS extension regex (TCP request content inspection) + ssl_sni - SNI TLS extension matches (TCP request content inspection) + ssl_sni_sub - SNI TLS extension contains (TCP request content inspection) + stopping - HAProxy process is currently stopping + url_param - URL parameter contains + var - Compare the value of a variable + wait_end - Inspection period is over Custom condition (option pass-through) @@ -3496,7 +3496,7 @@ - 1 + 1 Y @@ -4352,13 +4352,13 @@ Y dom - beg – key begins with requested value - dom – Domains - end – key ends with requested value - int – Integers - ip – IPs - reg – Regular Expressions - str – Strings + beg - key begins with requested value + dom - Domains + end - key ends with requested value + int - Integers + ip - IPs + reg - Regular Expressions + str - Strings diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M5_0_0.php b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M5_0_0.php index 04f55aae8..9546c29ee 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M5_0_0.php +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/Migrations/M5_0_0.php @@ -223,7 +223,7 @@ class M5_0_0 extends BaseModelMigration $action->http_response_option = (string)$action->http_response_set_status_code . $status_reason; $action->http_response_set_status_code = null; $action->http_response_set_status_reason = null; - } + } break; case 'http-response_set-var': $action->type = 'http-response'; From 92fb6dcb2d2f9462a47cac0c95af70bf42d21f3e Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Mon, 9 Feb 2026 07:51:37 +0100 Subject: [PATCH 04/87] security/q-feeds-connector: wrap up this revision --- security/q-feeds-connector/Makefile | 1 + security/q-feeds-connector/pkg-descr | 1 + .../q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py | 2 +- .../src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/security/q-feeds-connector/Makefile b/security/q-feeds-connector/Makefile index ae71aa8ba..70ee9f0eb 100644 --- a/security/q-feeds-connector/Makefile +++ b/security/q-feeds-connector/Makefile @@ -1,5 +1,6 @@ PLUGIN_NAME= q-feeds-connector PLUGIN_VERSION= 1.4 +PLUGIN_REVISION= 1 PLUGIN_COMMENT= Connector for Q-Feeds threat intel PLUGIN_MAINTAINER= devel@qfeeds.com PLUGIN_TIER= 2 diff --git a/security/q-feeds-connector/pkg-descr b/security/q-feeds-connector/pkg-descr index 789ff8663..08c3c9cb7 100644 --- a/security/q-feeds-connector/pkg-descr +++ b/security/q-feeds-connector/pkg-descr @@ -6,6 +6,7 @@ Plugin Changelog 1.4 * Feature: Added DNSCrypt-Proxy integration +* Bugfix: Track loaded lists when deselected and reload Unbounds blocklist in that case 1.3 diff --git a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py index 64190d6e0..7973d59f4 100755 --- a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py +++ b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py @@ -1,7 +1,7 @@ #!/usr/local/bin/python3 """ - Copyright (c) 2025-2026 Deciso B.V. + Copyright (c) 2025 Deciso B.V. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py b/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py index 8d2f2c955..d880d1e86 100755 --- a/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py +++ b/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py @@ -1,7 +1,7 @@ #!/usr/local/bin/python3 """ - Copyright (c) 2025 Deciso B.V. + Copyright (c) 2025-2026 Deciso B.V. All rights reserved. Redistribution and use in source and binary forms, with or without From 59d158e93a252d39324cb5cc8d0739e318a604ad Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Mon, 9 Feb 2026 07:52:45 +0100 Subject: [PATCH 05/87] LICENSE: sync --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 8544019fd..c0b094a0d 100644 --- a/LICENSE +++ b/LICENSE @@ -31,7 +31,7 @@ Copyright (c) 2019 Felix Matouschek Copyright (c) 2025 Florian Latifi Copyright (c) 2024 Francisco Dimattia Copyright (c) 2014-2025 Franco Fichtner -Copyright (c) 2016-2025 Frank Wall +Copyright (c) 2016-2026 Frank Wall Copyright (c) 2021 Github-jjw Copyright (c) 2023 Greg Glockner Copyright (c) 2024 Hasan Ucak From f64be105b0d5fbff5df0f7ab1d7b933e7c03159c Mon Sep 17 00:00:00 2001 From: Q-Feeds Date: Mon, 9 Feb 2026 10:52:52 +0100 Subject: [PATCH 06/87] Fix: Strip whitespace from API token to prevent 401 authentication errors (#5203) --- .../q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py index 6f2bb6a3c..749abeec9 100755 --- a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py +++ b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py @@ -37,7 +37,7 @@ class QFeedsConfig: cnf = ConfigParser() cnf.read(config_filename) if cnf.has_section('api') and cnf.has_option('api', 'key'): - self.api_key = cnf.get('api', 'key') + self.api_key = cnf.get('api', 'key').strip() class Api: From 0a6ed55f61db280d99ca98f12896ab253b807b2d Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Mon, 9 Feb 2026 11:27:46 +0100 Subject: [PATCH 07/87] security/acme-client: release 4.14 --- security/acme-client/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/acme-client/Makefile b/security/acme-client/Makefile index 9f8906b31..3d9fa6faf 100644 --- a/security/acme-client/Makefile +++ b/security/acme-client/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= acme-client -PLUGIN_VERSION= 4.13 +PLUGIN_VERSION= 4.14 PLUGIN_COMMENT= ACME Client PLUGIN_MAINTAINER= opnsense@moov.de PLUGIN_DEPENDS= acme.sh py${PLUGIN_PYTHON}-dns-lexicon From 911c1ab5fca860b884386e4fccec2a42b3e8a0cd Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Mon, 9 Feb 2026 11:48:11 +0100 Subject: [PATCH 08/87] security/acme-client: fix class name of Google Domains DNS API --- security/acme-client/pkg-descr | 5 +++++ .../OPNsense/AcmeClient/LeValidation/DnsGoogledomains.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/security/acme-client/pkg-descr b/security/acme-client/pkg-descr index 9fbe37ce3..b3df214a1 100644 --- a/security/acme-client/pkg-descr +++ b/security/acme-client/pkg-descr @@ -8,6 +8,11 @@ WWW: https://github.com/acmesh-official/acme.sh Plugin Changelog ================ +4.14 + +Fixed: +* fix class name of Google Domains DNS API (to make PHP linter happy) + 4.13 Added: diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/DnsGoogledomains.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/DnsGoogledomains.php index d6119cad6..f367ff319 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/DnsGoogledomains.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeValidation/DnsGoogledomains.php @@ -34,7 +34,7 @@ use OPNsense\Core\Config; * Google Domains DNS API * @package OPNsense\AcmeClient */ -class DnsGoogleDomains extends Base implements LeValidationInterface +class DnsGoogledomains extends Base implements LeValidationInterface { public function prepare() { From 3aa7c39481e8e9218ee716d67246da5e04255644 Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Mon, 9 Feb 2026 17:04:33 +0100 Subject: [PATCH 09/87] net/haproxy: support new map file type "sub" --- net/haproxy/pkg-descr | 2 +- .../src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/net/haproxy/pkg-descr b/net/haproxy/pkg-descr index 0af36b5e2..75ae2c0e4 100644 --- a/net/haproxy/pkg-descr +++ b/net/haproxy/pkg-descr @@ -18,7 +18,7 @@ Added: * add new condition: HTTP method * support custom HTTP status code in "http-request deny" rules * add new backend option to control PROXY protocol for health checks (#2909) -* add support for new map file types: beg,end,int,ip,reg,str (#3641) +* add support for new map file types: beg,end,int,ip,reg,str,sub (#3641) * add support for more sample fetches: quic_enabled, stopping, wait_end (#3702) * add support for HTTP compression (#4867) * add all action keywords for http-request/-response and tcp-request/-response rules diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index fb4a4b8e0..b6ee4174e 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -4359,6 +4359,7 @@ ip - IPs reg - Regular Expressions str - Strings + sub - substring matches requested value From 6c779f06903c0c2d9e1198095638a61cbaca7173 Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Mon, 9 Feb 2026 23:26:26 +0100 Subject: [PATCH 10/87] net/haproxy: fix syntax of set-var-fmt --- .../opnsense/service/templates/OPNsense/HAProxy/haproxy.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf index bf0ad72d8..e722821ee 100644 --- a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf @@ -1499,6 +1499,8 @@ {% set action_keyword_data = 'lua.' ~ action_data.http_request_option %} {% elif action_data.http_request_action == 'set-var' %} {% set action_keyword_data = 'set-var' ~ action_data.http_request_option %} +{% elif action_data.http_request_action == 'set-var-fmt' %} +{% set action_keyword_data = 'set-var-fmt' ~ action_data.http_request_option %} {% elif action_data.http_request_action == 'use-service' %} {% set action_keyword_data = 'use-service lua.' ~ action_data.http_request_option %} {% else %} From 85f1bb94bf292e031fd006728c9db49a6b9ae2be Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Tue, 10 Feb 2026 11:30:50 +0100 Subject: [PATCH 11/87] www/web-proxy-sso: model style --- .../mvc/app/models/OPNsense/ProxySSO/ProxySSO.xml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/www/web-proxy-sso/src/opnsense/mvc/app/models/OPNsense/ProxySSO/ProxySSO.xml b/www/web-proxy-sso/src/opnsense/mvc/app/models/OPNsense/ProxySSO/ProxySSO.xml index 85760d927..fade5a551 100644 --- a/www/web-proxy-sso/src/opnsense/mvc/app/models/OPNsense/ProxySSO/ProxySSO.xml +++ b/www/web-proxy-sso/src/opnsense/mvc/app/models/OPNsense/ProxySSO/ProxySSO.xml @@ -1,8 +1,6 @@ //OPNsense/ProxySSO - - Web-proxy Single Sign-On plugin - + Web-proxy Single Sign-On plugin 0 @@ -16,8 +14,6 @@ Windows 2008 with AES - - N - + From fb59f87e9993ce9894669309db74a0bffdc93328 Mon Sep 17 00:00:00 2001 From: Andrei Hodorog Date: Tue, 10 Feb 2026 15:31:42 +0000 Subject: [PATCH 12/87] dns/dnscrypt-proxy: fix bootstrap_resolvers with multiple comma-separated servers (#5163) When multiple bootstrap resolvers are configured in the "Fallback Resolver" field (e.g., "1.1.1.1:53,9.9.9.9:53"), the generated config incorrectly places the comma inside a single string: bootstrap_resolvers = ['1.1.1.1:53,9.9.9.9:53'] This causes dnscrypt-proxy to fail with: [FATAL] Bootstrap resolver [...]: Host does not parse as IP '1.1.1.1:53,9.9.9.9:53' The fix applies the same split/join pattern already used for listen_addresses, server_names, disabled_server_names, and relaylist in the same template: bootstrap_resolvers = ['1.1.1.1:53','9.9.9.9:53'] This bug was introduced in commit 1eec51a65 which renamed fallback_resolver to bootstrap_resolvers but did not update the template syntax from a single string to a TOML array format. --- .../templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml b/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml index 84d98ff08..7f19af4f9 100644 --- a/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml +++ b/dns/dnscrypt-proxy/src/opnsense/service/templates/OPNsense/Dnscryptproxy/dnscrypt-proxy.toml @@ -95,7 +95,7 @@ tls_disable_session_tickets = true tls_disable_session_tickets = false {% endif %} -bootstrap_resolvers = ['{{ OPNsense.dnscryptproxy.general.fallback_resolver }}'] +bootstrap_resolvers = ['{{ OPNsense.dnscryptproxy.general.fallback_resolver.split(',') | join("','") }}'] {% if helpers.exists('OPNsense.dnscryptproxy.general.ignore_system_dns') and OPNsense.dnscryptproxy.general.ignore_system_dns == '1' %} ignore_system_dns = true From cca920acb0f5925b2451999ea39baaef67f2cecc Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Tue, 10 Feb 2026 17:04:38 +0100 Subject: [PATCH 13/87] net/haproxy: support converts in more ACLs --- net/haproxy/pkg-descr | 5 ++ .../templates/OPNsense/HAProxy/haproxy.conf | 77 ++++++++++++++++--- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/net/haproxy/pkg-descr b/net/haproxy/pkg-descr index 75ae2c0e4..e7c26f768 100644 --- a/net/haproxy/pkg-descr +++ b/net/haproxy/pkg-descr @@ -6,6 +6,11 @@ very high loads while needing persistence or Layer7 processing. Plugin Changelog ================ +5.1 + +Added: +* more conditions have support for converters + 5.0 WARNING: This is a new major release, which may result in diff --git a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf index e722821ee..2e9c90d76 100644 --- a/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf +++ b/net/haproxy/src/opnsense/service/templates/OPNsense/HAProxy/haproxy.conf @@ -448,7 +448,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_conn_cnt(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_conn_cnt_comparison ~ ' ' ~ acl_data.sc_conn_cnt) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_conn_cnt(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_conn_cnt_comparison ~ ' ' ~ acl_data.sc_conn_cnt) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -460,7 +465,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_conn_cur(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_conn_cur_comparison ~ ' ' ~ acl_data.sc_conn_cur) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_conn_cur(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_conn_cur_comparison ~ ' ' ~ acl_data.sc_conn_cur) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -472,7 +482,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_conn_rate(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_conn_rate_comparison ~ ' ' ~ acl_data.sc_conn_rate) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_conn_rate(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_conn_rate_comparison ~ ' ' ~ acl_data.sc_conn_rate) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -784,7 +799,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_http_err_cnt(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_http_err_cnt_comparison ~ ' ' ~ acl_data.sc_http_err_cnt) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_http_err_cnt(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_http_err_cnt_comparison ~ ' ' ~ acl_data.sc_http_err_cnt) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -796,7 +816,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_http_err_rate(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_http_err_rate_comparison ~ ' ' ~ acl_data.sc_http_err_rate) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_http_err_rate(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_http_err_rate_comparison ~ ' ' ~ acl_data.sc_http_err_rate) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -808,7 +833,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_http_fail_cnt(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_http_fail_cnt_comparison ~ ' ' ~ acl_data.sc_http_fail_cnt) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_http_fail_cnt(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_http_fail_cnt_comparison ~ ' ' ~ acl_data.sc_http_fail_cnt) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -820,7 +850,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_http_fail_rate(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_http_fail_rate_comparison ~ ' ' ~ acl_data.sc_http_fail_rate) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_http_fail_rate(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_http_fail_rate_comparison ~ ' ' ~ acl_data.sc_http_fail_rate) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -832,7 +867,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_http_req_cnt(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_http_req_cnt_comparison ~ ' ' ~ acl_data.sc_http_req_cnt) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_http_req_cnt(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_http_req_cnt_comparison ~ ' ' ~ acl_data.sc_http_req_cnt) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -844,7 +884,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_http_req_rate(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_http_req_rate_comparison ~ ' ' ~ acl_data.sc_http_req_rate) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_http_req_rate(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_http_req_rate_comparison ~ ' ' ~ acl_data.sc_http_req_rate) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -964,7 +1009,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_sess_cnt(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_sess_cnt_comparison ~ ' ' ~ acl_data.sc_sess_cnt) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_sess_cnt(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_sess_cnt_comparison ~ ' ' ~ acl_data.sc_sess_cnt) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters @@ -976,7 +1026,12 @@ {% else %} {% set table_data = '' %} {% endif %} -{% do acl_options.append('sc_sess_rate(' ~ acl_data.sc_number ~ table_data ~ ') ' ~ acl_data.sc_sess_rate_comparison ~ ' ' ~ acl_data.sc_sess_rate) %} +{% if acl_data.converter|default("") != "" %} +{% set converter_data = ',' ~ acl_data.converter %} +{% else %} +{% set converter_data = '' %} +{% endif %} +{% do acl_options.append('sc_sess_rate(' ~ acl_data.sc_number ~ table_data ~ ')' ~ converter_data ~ ' ' ~ acl_data.sc_sess_rate_comparison ~ ' ' ~ acl_data.sc_sess_rate) %} {% else %} {% set acl_enabled = '0' %} # ERROR: missing parameters From ae5b72b5e521fc68b4da590153bd4eb85ab571ea Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Tue, 10 Feb 2026 17:05:02 +0100 Subject: [PATCH 14/87] net/haproxy: release 5.1 --- net/haproxy/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/haproxy/Makefile b/net/haproxy/Makefile index bc5ac0c84..fc7cf3517 100644 --- a/net/haproxy/Makefile +++ b/net/haproxy/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= haproxy -PLUGIN_VERSION= 5.0 +PLUGIN_VERSION= 5.1 PLUGIN_COMMENT= Reliable, high performance TCP/HTTP load balancer PLUGIN_DEPENDS= haproxy py${PLUGIN_PYTHON}-haproxy-cli PLUGIN_MAINTAINER= opnsense@moov.de From 8701588fad5899c6126f5f2d185ff45ca1724f5a Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Wed, 11 Feb 2026 14:31:07 +0100 Subject: [PATCH 15/87] dns/dnscrypt-proxy: wrap up revision --- dns/dnscrypt-proxy/Makefile | 2 +- dns/dnscrypt-proxy/pkg-descr | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dns/dnscrypt-proxy/Makefile b/dns/dnscrypt-proxy/Makefile index b0e6868f1..cf19aed0a 100644 --- a/dns/dnscrypt-proxy/Makefile +++ b/dns/dnscrypt-proxy/Makefile @@ -1,6 +1,6 @@ PLUGIN_NAME= dnscrypt-proxy PLUGIN_VERSION= 1.16 -PLUGIN_REVISION= 1 +PLUGIN_REVISION= 2 PLUGIN_COMMENT= Flexible DNS proxy supporting DNSCrypt and DoH PLUGIN_DEPENDS= dnscrypt-proxy2 PLUGIN_MAINTAINER= m.muenz@gmail.com diff --git a/dns/dnscrypt-proxy/pkg-descr b/dns/dnscrypt-proxy/pkg-descr index f06697ce8..bc9af91cf 100644 --- a/dns/dnscrypt-proxy/pkg-descr +++ b/dns/dnscrypt-proxy/pkg-descr @@ -7,6 +7,7 @@ Plugin Changelog 1.16 * Fix ODoH servers not working (contributed by Pascal Herget) +* Fix bootstrap_resolvers with multiple comma-separated servers (contributed by Andrei Hodorog) 1.15 From a444a16214d26f490a60866bc9c62ad32a1ca44d Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Wed, 11 Feb 2026 16:11:03 +0100 Subject: [PATCH 16/87] security/acme-client: fix truenas automations, closes #5210 regression introduced in #5157 --- security/acme-client/pkg-descr | 1 + .../AcmeClient/forms/dialogAction.xml | 36 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/security/acme-client/pkg-descr b/security/acme-client/pkg-descr index b3df214a1..3c5d7f5cf 100644 --- a/security/acme-client/pkg-descr +++ b/security/acme-client/pkg-descr @@ -12,6 +12,7 @@ Plugin Changelog Fixed: * fix class name of Google Domains DNS API (to make PHP linter happy) +* parameters for TrueNAS automation not visible (#5210) 4.13 diff --git a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml index d518bdeaa..865546d33 100644 --- a/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml +++ b/security/acme-client/src/opnsense/mvc/app/controllers/OPNsense/AcmeClient/forms/dialogAction.xml @@ -377,6 +377,24 @@ header + + action.acme_truenas_apikey + + text + API key generated in the TrueNAS web UI. + + + action.acme_truenas_hostname + + text + Hostname or IP address of TrueNAS Core Server. + + + action.acme_truenas_scheme + + dropdown + Connection scheme that will be used when uploading certificates to TrueNAS Core Server. + header @@ -399,24 +417,6 @@ password - - action.acme_truenas_apikey - - text - API key generated in the TrueNAS web UI. - - - action.acme_truenas_hostname - - text - Hostname or IP address of TrueNAS Core Server. - - - action.acme_truenas_scheme - - dropdown - Connection scheme that will be used when uploading certificates to TrueNAS Core Server. - header From b9b11409103a61156503272c6a6a1bbc4a8409d4 Mon Sep 17 00:00:00 2001 From: Nuadh123 Date: Wed, 11 Feb 2026 19:18:58 +0100 Subject: [PATCH 17/87] os-nextcloud-backup Add support for having backing up to a subdirectory instead of the root backupdir (#5191) --- .../mvc/app/library/OPNsense/Backup/Nextcloud.php | 11 +++++++++++ .../app/models/OPNsense/Backup/NextcloudSettings.xml | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php index 6f72ba479..3457e4b15 100644 --- a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php +++ b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php @@ -84,6 +84,13 @@ class Nextcloud extends Base implements IBackupProvider "type" => "text", "label" => gettext("Directory Name without leading slash, starting from user's root"), "value" => 'OPNsense-Backup' + ), + array( + "name" => "addhostname", + "type" => "checkbox", + "label" => gettext("Backup to directory named after hostname"), + "help" => gettext("Create subdirectory under backupdir for this host"), + "value" => null ) ); $nextcloud = new NextcloudSettings(); @@ -139,6 +146,10 @@ class Nextcloud extends Base implements IBackupProvider $backupdir = (string)$nextcloud->backupdir; $crypto_password = (string)$nextcloud->password_encryption; + if (!$nextcloud->addhostname->isEmpty()) { + $backupdir .= "/".gethostname()."/"; + } + // Check if destination directory exists, create (full path) if not try { $internal_username = $this->getInternalUsername($url, $username, $password); diff --git a/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml b/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml index 10a12429e..e5c5b10c3 100644 --- a/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml +++ b/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml @@ -1,6 +1,6 @@ //system/backup/nextcloud - 1.0.0 + 1.0.1 OPNsense Nextcloud Backup Settings @@ -49,5 +49,9 @@ OPNsense-Backup The Backup Directory can only consist of alphanumeric characters, dash, underscores and slash. No leading or trailing slash. + + 1 + Y + From 449323e6a505b78592ce5bc70a385383aeb9821d Mon Sep 17 00:00:00 2001 From: Nuadh123 Date: Wed, 11 Feb 2026 19:21:09 +0100 Subject: [PATCH 18/87] os-nextcloud-backup Skip non-files when enumerating local entries to backup (#5192) --- .../opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php index 3457e4b15..e606c8516 100644 --- a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php +++ b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php @@ -161,11 +161,14 @@ class Nextcloud extends Base implements IBackupProvider // Get list of files from local backup system $local_files = array(); $tmp_local_files = scandir('/conf/backup/'); - // Remove '.' and '..' + // Remove '.' and '..', skip directories foreach ($tmp_local_files as $tmp_local_file) { if ($tmp_local_file === '.' || $tmp_local_file === '..') { continue; } + if (!is_file("/conf/backup/".$tmp_local_file)) { + continue; + } $local_files[] = $tmp_local_file; } From de4c98eee25b26850bae87ac4fce68e794452e01 Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Mon, 16 Feb 2026 16:58:17 +0100 Subject: [PATCH 19/87] Security: Q-Feeds Connect - add new options as available in integrated blocklists (#5226) * Security: Q-Feeds Connect - add new options as available in integrated blocklists, closes https://github.com/opnsense/plugins/issues/5197 This adds allowlists (regex patterns), source_nets Q-Feeds applies on, address to return and optional NXDOMAIN responses. Please note this version is only compatible with current community versions, business edition installs will have to wait for 26.4. * Security: Q-Feeds Connect - update version and changelog --- security/q-feeds-connector/Makefile | 3 +- security/q-feeds-connector/pkg-descr | 8 +++ .../OPNsense/QFeeds/forms/settings.xml | 38 ++++++++++++++ .../app/models/OPNsense/QFeeds/Connector.xml | 16 ++++++ .../mvc/app/views/OPNsense/QFeeds/index.volt | 7 +++ .../scripts/unbound/blocklists/qfeeds_bl.py | 50 ++++++++++++++++--- .../OPNsense/QFeeds/qfeeds-blocklists.conf | 7 +++ 7 files changed, 121 insertions(+), 8 deletions(-) diff --git a/security/q-feeds-connector/Makefile b/security/q-feeds-connector/Makefile index 70ee9f0eb..f690fad92 100644 --- a/security/q-feeds-connector/Makefile +++ b/security/q-feeds-connector/Makefile @@ -1,6 +1,5 @@ PLUGIN_NAME= q-feeds-connector -PLUGIN_VERSION= 1.4 -PLUGIN_REVISION= 1 +PLUGIN_VERSION= 1.5 PLUGIN_COMMENT= Connector for Q-Feeds threat intel PLUGIN_MAINTAINER= devel@qfeeds.com PLUGIN_TIER= 2 diff --git a/security/q-feeds-connector/pkg-descr b/security/q-feeds-connector/pkg-descr index 08c3c9cb7..699c60849 100644 --- a/security/q-feeds-connector/pkg-descr +++ b/security/q-feeds-connector/pkg-descr @@ -3,6 +3,14 @@ Connector for Q-Feeds threat intel Plugin Changelog ================ +1.5 + +* Feature: Add passlist option for unbound +* Feature: Add effective networks for unbound +* Feature: Add NXDOMAIN option for unbound +* Feature: Add dest address for unbound + + 1.4 * Feature: Added DNSCrypt-Proxy integration diff --git a/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml b/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml index 0469ed6ca..acb834253 100644 --- a/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml +++ b/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml @@ -15,4 +15,42 @@ checkbox Use domain feeds in Unbound DNS blocklist, requires blocklists to be enabled in order to have effect + + header + + + + + connect.unbound.allowlists + + select_multiple + + true + List of domains to allow. You can use regular expressions. This allow list only applies to blocklist matches on items in this policy. + + + connect.unbound.source_nets + + select_multiple + + true + Source networks to apply policy on. Examples are 192.168.1.0/24 or 192.168.1.1. Leave empty to apply on everything. All specified networks should use the same protocol family and have equal sizes to avoid priority issues. + + + connect.unbound.address + + text + true + + Destination ip address for entries in the blocklist (leave empty to use default: 0.0.0.0). + Not used when "Return NXDOMAIN" is checked. + + + + connect.unbound.nxdomain + + checkbox + true + Use the DNS response code NXDOMAIN instead of a destination address. + diff --git a/security/q-feeds-connector/src/opnsense/mvc/app/models/OPNsense/QFeeds/Connector.xml b/security/q-feeds-connector/src/opnsense/mvc/app/models/OPNsense/QFeeds/Connector.xml index fb158adcf..d748a54ef 100644 --- a/security/q-feeds-connector/src/opnsense/mvc/app/models/OPNsense/QFeeds/Connector.xml +++ b/security/q-feeds-connector/src/opnsense/mvc/app/models/OPNsense/QFeeds/Connector.xml @@ -7,5 +7,21 @@ + + + + Y + Y + Please specify a valid network segment or address (IPv4/IPv6). If a mask is provided, please omit the host bits. + N + N + Y + +
+ N + ipv4 +
+ +
diff --git a/security/q-feeds-connector/src/opnsense/mvc/app/views/OPNsense/QFeeds/index.volt b/security/q-feeds-connector/src/opnsense/mvc/app/views/OPNsense/QFeeds/index.volt index 21d3b54c7..c015c8094 100644 --- a/security/q-feeds-connector/src/opnsense/mvc/app/views/OPNsense/QFeeds/index.volt +++ b/security/q-feeds-connector/src/opnsense/mvc/app/views/OPNsense/QFeeds/index.volt @@ -99,6 +99,13 @@ POSSIBILITY OF SUCH DAMAGE. } }); + $("#connect\\.general\\.enable_unbound_bl").change(function(){ + if ($(this).is(':checked')) { + $(".unbound_options").closest('table').show(); + } else { + $(".unbound_options").closest('table').hide(); + } + }); let selected_tab = window.location.hash != "" ? window.location.hash : "#settings"; $('a[href="' +selected_tab + '"]').tab('show'); diff --git a/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py b/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py index d880d1e86..1675557ba 100755 --- a/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py +++ b/security/q-feeds-connector/src/opnsense/scripts/unbound/blocklists/qfeeds_bl.py @@ -27,12 +27,19 @@ """ import os +import re +import syslog +import uuid from . import BaseBlocklistHandler -class DefaultBlocklistHandler(BaseBlocklistHandler): +class QFeedsBlocklistHandler(BaseBlocklistHandler): def __init__(self): super().__init__('/usr/local/etc/unbound/qfeeds-blocklists.conf') self.priority = 50 + self._compat_id = str(uuid.uuid4()) + + def _is_enabled(self): + return self.cnf and self.cnf.has_section('settings') and self.cnf.has_option('settings', 'filenames') def get_config(self): # do not use, unbound worker settings @@ -41,11 +48,10 @@ class DefaultBlocklistHandler(BaseBlocklistHandler): def get_blocklist(self): # Only return domains if integration is enabled (filenames are offered) qfeeds_filenames = [] - if self.cnf and self.cnf.has_section('settings'): - if self.cnf.has_option('settings', 'filenames'): - qfeeds_filenames = self.cnf.get('settings', 'filenames').split(',') - # touch a file to help qfeedsctl detect the current instance uses its list - open('/tmp/qfeeds-unbound-bl.stat', 'w').write('') + if self._is_enabled(): + qfeeds_filenames = self.cnf.get('settings', 'filenames').split(',') + # touch a file to help qfeedsctl detect the current instance uses its list + open('/tmp/qfeeds-unbound-bl.stat', 'w').write('') result = {} for filename in qfeeds_filenames: @@ -58,3 +64,35 @@ class DefaultBlocklistHandler(BaseBlocklistHandler): def get_passlist_patterns(self): return [] + + def get_policies(self): + if not self._is_enabled(): + return [] + + cfg = { + 'source_nets': [], + 'address': '', + 'rcode': '', + 'id': self._compat_id, + 'allowlists': [] + } + for k,v in self.cnf['settings'].items(): + if k in cfg and v.strip() != '': + if type(cfg[k]) is list: + cfg[k] = v.split(',') + else: + cfg[k] = v.strip() + + if cfg['allowlists']: + compiled_passlist = set() + for pattern in cfg['allowlists']: + try: + re.compile(pattern, re.IGNORECASE) + compiled_passlist.add(pattern) + except re.error: + syslog.syslog(syslog.LOG_ERR,'Q-Feeds : skip invalid whitelist exclude pattern "%s"' % pattern) + + cfg['passlist'] = '|'.join(compiled_passlist) + del cfg['allowlists'] + + return [cfg] \ No newline at end of file diff --git a/security/q-feeds-connector/src/opnsense/service/templates/OPNsense/QFeeds/qfeeds-blocklists.conf b/security/q-feeds-connector/src/opnsense/service/templates/OPNsense/QFeeds/qfeeds-blocklists.conf index 611317775..fb4bbd7e4 100644 --- a/security/q-feeds-connector/src/opnsense/service/templates/OPNsense/QFeeds/qfeeds-blocklists.conf +++ b/security/q-feeds-connector/src/opnsense/service/templates/OPNsense/QFeeds/qfeeds-blocklists.conf @@ -2,4 +2,11 @@ not helpers.empty('OPNsense.QFeedsConnector.general.enable_unbound_bl') %} [settings] filenames=/var/db/qfeeds-tables/malware_domains.txt +{% if not helpers.empty('OPNsense.QFeedsConnector.unbound') %} +allowlists={{OPNsense.QFeedsConnector.unbound.allowlists|default('')}} +source_nets={{OPNsense.QFeedsConnector.unbound.source_nets|default('')}} +address={{OPNsense.QFeedsConnector.unbound.address|default('0.0.0.0')}} +rcode={% if OPNsense.QFeedsConnector.unbound.nxdomain|default('0') == '1' %}NXDOMAIN{%else%}NOERROR{%endif +%} +cache_ttl={{OPNsense.QFeedsConnector.unbound.cache_ttl|default('72000')}} +{% endif %} {% endif %} From 3b1c816363b8a35f6edcea3074caeb7d6106a78b Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Mon, 16 Feb 2026 22:49:45 +0100 Subject: [PATCH 20/87] net/haproxy: fix migration of lua rules, closes #5225 --- net/haproxy/pkg-descr | 3 +++ .../opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/net/haproxy/pkg-descr b/net/haproxy/pkg-descr index e7c26f768..8a53fdd80 100644 --- a/net/haproxy/pkg-descr +++ b/net/haproxy/pkg-descr @@ -11,6 +11,9 @@ Plugin Changelog Added: * more conditions have support for converters +Fixed: +* migration fails if a http-request/-response "lua" rule is configured + 5.0 WARNING: This is a new major release, which may result in diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index b6ee4174e..83cf0a2ca 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -3646,6 +3646,7 @@ do-log do-resolve early-hint + lua normalize-uri redirect reject @@ -3689,7 +3690,7 @@ track-sc1 track-sc2 unset-var - use-service + use-service - use a lua service wait-for-body wait-for-handshake @@ -3711,6 +3712,7 @@ del-map deny do-log + lua redirect replace-header replace-value @@ -3804,7 +3806,7 @@ content track-sc1 content track-sc2 content unset-var - content use-service + content use-service - use a lua service inspect-delay session accept session attach-srv From f5af443a43ed9d577930d47f7171173a9e5c6763 Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Tue, 17 Feb 2026 00:04:28 +0100 Subject: [PATCH 21/87] net/haproxy: add support for mapfile URLs, refs #4825 --- net/haproxy/pkg-descr | 1 + .../OPNsense/HAProxy/forms/dialogMapfile.xml | 8 ++- .../app/models/OPNsense/HAProxy/HAProxy.xml | 6 ++- .../OPNsense/HAProxy/exportMapFiles.php | 54 +++++++++++++++++-- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/net/haproxy/pkg-descr b/net/haproxy/pkg-descr index 8a53fdd80..48c8151c5 100644 --- a/net/haproxy/pkg-descr +++ b/net/haproxy/pkg-descr @@ -10,6 +10,7 @@ Plugin Changelog Added: * more conditions have support for converters +* add support for mapfile URLs (#4825) Fixed: * migration fails if a http-request/-response "lua" rule is configured diff --git a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml index 8c7d74b41..23a439474 100644 --- a/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml +++ b/net/haproxy/src/opnsense/mvc/app/controllers/OPNsense/HAProxy/forms/dialogMapfile.xml @@ -21,6 +21,12 @@ mapfile.content textbox - HAProxy documentation for a full description.]]> + HAProxy documentation for a full description.]]> + + + mapfile.url + + text + diff --git a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml index 83cf0a2ca..4d0ae9b9b 100644 --- a/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml +++ b/net/haproxy/src/opnsense/mvc/app/models/OPNsense/HAProxy/HAProxy.xml @@ -4365,8 +4365,12 @@ - Y + N + + /^.{1,4096}$/u + N + diff --git a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportMapFiles.php b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportMapFiles.php index fe2e682f7..eebeaa773 100755 --- a/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportMapFiles.php +++ b/net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/exportMapFiles.php @@ -2,7 +2,7 @@ OPNsense->HAProxy->mapfiles)) { foreach ($configObj->OPNsense->HAProxy->mapfiles->children() as $mapfile) { $mf_name = (string)$mapfile->name; $mf_id = (string)$mapfile->id; + $mf_url = (string)$mapfile->url; if ($mf_id != "") { - $mf_content = htmlspecialchars_decode(str_replace("\r", "", (string)$mapfile->content)); $mf_filename = $export_path . $mf_id . ".txt"; - file_put_contents($mf_filename, $mf_content); - chmod($mf_filename, 0600); - echo "map file exported to " . $mf_filename . "\n"; + // Download file from URL (if URL was provided). + try { + if ($mf_url == "") { + throw new \Exception("no URL provided"); + } + $fp = fopen($mf_filename, 'wb'); + if ($fp === false) { + throw new \Exception("unable to open {$mf_filename} for writing"); + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $mf_url); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_TIMEOUT, 60); + curl_setopt($ch, CURLOPT_FAILONERROR, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + + if (!curl_exec($ch)) { + throw new \Exception("download error: " . curl_error($ch)); + } + echo "map file downloaded to " . $mf_filename . "\n"; + } catch (\Exception $e) { + // Show error message only if URL was specified. + if ($mf_url != "") { + echo "download of map file failed, error: " . $e->getMessage() . "\n"; + echo "trying to fill map file with fallback content\n"; + $mf_content = "# NOTE: Download failed, this is the fallback content.\n"; + } else { + $mf_content = ''; + } + + // Write contents to map file. + // This is also used as a fallback if map file download fails. + $mf_content = $mf_content . htmlspecialchars_decode(str_replace("\r", "", (string)$mapfile->content)); + file_put_contents($mf_filename, $mf_content); + echo "map file exported to " . $mf_filename . "\n"; + } finally { + if (isset($ch)) { + curl_close($ch); + } + if (isset($fp) && is_resource($fp)) { + fclose($fp); + } + chmod($mf_filename, 0600); + chown($mf_filename, 'www'); + } } } } From 1bfb448cf26b3297102ba7359f79bbdad4345ae4 Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Wed, 18 Feb 2026 09:07:18 +0100 Subject: [PATCH 22/87] net/isc-dhcp: move ip_in_interface_alias_subnet() here Only called by this plugin. So we can ditch it from core. --- net/isc-dhcp/Makefile | 2 +- .../src/etc/inc/plugins.inc.d/dhcpd.inc | 18 ++++++++++++++++++ net/isc-dhcp/src/www/services_dhcp.php | 2 +- net/isc-dhcp/src/www/services_dhcp_edit.php | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/net/isc-dhcp/Makefile b/net/isc-dhcp/Makefile index 970944acb..1b8e9c572 100644 --- a/net/isc-dhcp/Makefile +++ b/net/isc-dhcp/Makefile @@ -1,6 +1,6 @@ PLUGIN_NAME= isc-dhcp PLUGIN_VERSION= 1.0 -PLUGIN_REVISION= 3 +PLUGIN_REVISION= 4 PLUGIN_COMMENT= ISC DHCPv4/v6 server PLUGIN_DEPENDS= isc-dhcp44-server PLUGIN_MAINTAINER= franco@opnsense.org diff --git a/net/isc-dhcp/src/etc/inc/plugins.inc.d/dhcpd.inc b/net/isc-dhcp/src/etc/inc/plugins.inc.d/dhcpd.inc index df22829e0..156f0e840 100644 --- a/net/isc-dhcp/src/etc/inc/plugins.inc.d/dhcpd.inc +++ b/net/isc-dhcp/src/etc/inc/plugins.inc.d/dhcpd.inc @@ -1325,3 +1325,21 @@ function dhcpd_staticarp($interface, $ifconfig_details) interfaces_neighbors_configure($interface, $ifconfig_details); } } + +function dhcpd_ip_in_interface_alias_subnet($interface, $ipalias) +{ + if (empty($interface) || !is_ipaddr($ipalias)) { + return false; + } + + foreach (config_read_array('virtualip', 'vip', false) as $vip) { + if ($vip['mode'] == 'ipalias' && $vip['interface'] == $interface) { + $subnet = is_ipaddrv6($ipalias) ? gen_subnetv6($vip['subnet'], $vip['subnet_bits']) : gen_subnet($vip['subnet'], $vip['subnet_bits']); + if (ip_in_subnet($ipalias, "{$subnet}/{$vip['subnet_bits']}")) { + return true; + } + } + } + + return false; +} diff --git a/net/isc-dhcp/src/www/services_dhcp.php b/net/isc-dhcp/src/www/services_dhcp.php index 6b00cc16e..ad87e530b 100644 --- a/net/isc-dhcp/src/www/services_dhcp.php +++ b/net/isc-dhcp/src/www/services_dhcp.php @@ -175,7 +175,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } list (, $parent_net) = interfaces_primary_address($pconfig['if']); if (is_subnetv4($parent_net) && $pconfig['gateway'] && $pconfig['gateway'] != "none") { - if (!ip_in_subnet($pconfig['gateway'], $parent_net) && !ip_in_interface_alias_subnet($pconfig['if'], $pconfig['gateway'])) { + if (!ip_in_subnet($pconfig['gateway'], $parent_net) && !dhcpd_ip_in_interface_alias_subnet($pconfig['if'], $pconfig['gateway'])) { $input_errors[] = sprintf(gettext("The gateway address %s does not lie within the chosen interface's subnet."), $pconfig['gateway']); } } diff --git a/net/isc-dhcp/src/www/services_dhcp_edit.php b/net/isc-dhcp/src/www/services_dhcp_edit.php index ceb180a70..f4eb8c807 100644 --- a/net/isc-dhcp/src/www/services_dhcp_edit.php +++ b/net/isc-dhcp/src/www/services_dhcp_edit.php @@ -167,7 +167,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } if (is_subnetv4($parent_net) && $pconfig['gateway'] != "none" && !empty($pconfig['gateway'])) { - if (!ip_in_subnet($pconfig['gateway'], $parent_net) && !ip_in_interface_alias_subnet($if, $pconfig['gateway'])) { + if (!ip_in_subnet($pconfig['gateway'], $parent_net) && !dhcpd_ip_in_interface_alias_subnet($if, $pconfig['gateway'])) { $input_errors[] = sprintf(gettext("The gateway address %s does not lie within the chosen interface's subnet."), $_POST['gateway']); } } From 58f0dfd86e75dfd9f3c09a7460912cc150e36a29 Mon Sep 17 00:00:00 2001 From: Q-Feeds Date: Thu, 19 Feb 2026 09:06:10 +0100 Subject: [PATCH 23/87] q-feeds-connector: Update help text to mention DNScrypt-proxy blocklists (#5237) Co-authored-by: Cursor --- .../mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml b/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml index acb834253..37e9dec8e 100644 --- a/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml +++ b/security/q-feeds-connector/src/opnsense/mvc/app/controllers/OPNsense/QFeeds/forms/settings.xml @@ -13,7 +13,7 @@ connect.general.enable_unbound_bl checkbox - Use domain feeds in Unbound DNS blocklist, requires blocklists to be enabled in order to have effect + Use domain feeds in Unbound DNS and DNScrypt-proxy blocklists, requires blocklists to be enabled in order to have effect header From 9250d4ddc8d31d694db828217742cc4f66c42aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <34602360+opnsenseuser@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:18:12 +0100 Subject: [PATCH 24/87] Theme cicada/vicuna/tukan some fixes (#5198) --- misc/theme-cicada/Makefile | 2 +- .../www/themes/cicada/build/css/opnsense-bootgrid.css | 5 ----- .../opnsense/www/themes/cicada/build/css/tabulator.min.css | 2 +- misc/theme-tukan/Makefile | 2 +- .../www/themes/tukan/build/css/opnsense-bootgrid.css | 7 +------ misc/theme-vicuna/Makefile | 2 +- .../www/themes/vicuna/build/css/opnsense-bootgrid.css | 5 ----- .../opnsense/www/themes/vicuna/build/css/tabulator.min.css | 2 +- 8 files changed, 6 insertions(+), 21 deletions(-) diff --git a/misc/theme-cicada/Makefile b/misc/theme-cicada/Makefile index e7d7ac36e..baff1111e 100644 --- a/misc/theme-cicada/Makefile +++ b/misc/theme-cicada/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= theme-cicada -PLUGIN_VERSION= 1.40 +PLUGIN_VERSION= 1.41 PLUGIN_COMMENT= The cicada theme - dark grey onyx PLUGIN_MAINTAINER= rene@team-rebellion.net PLUGIN_NO_ABI= yes diff --git a/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/opnsense-bootgrid.css b/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/opnsense-bootgrid.css index 9fe95901a..63af6ab07 100644 --- a/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/opnsense-bootgrid.css +++ b/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/opnsense-bootgrid.css @@ -210,11 +210,6 @@ padding-right: 20px; } -.bootgrid-footer-commands { - width: 90px; - padding-left: 8px; -} - .tabulator-tableholder::after { content: ""; display: block; diff --git a/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/tabulator.min.css b/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/tabulator.min.css index 7034e9655..aeff5d2d8 100644 --- a/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/tabulator.min.css +++ b/misc/theme-cicada/src/opnsense/www/themes/cicada/build/css/tabulator.min.css @@ -1,2 +1,2 @@ -.tabulator{background-color:#888;border:1px solid #191919;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none; }.tabulator .tabulator-header{background-color:#e6e6e6;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;outline:none;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#e6e6e6;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#cdcdcd;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{background-color:#d6d6d6;color:#000}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{background-color:#3876ca;color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#cdcdcd;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #151515}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #151515}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;display:inline-block}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;min-width:100%;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{inset:0;pointer-events:none;position:absolute;z-index:10}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{border:1px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;position:absolute;right:-3px;width:6px}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{border:2px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-footer{background-color:#e6e6e6;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:none;display:inline-block;font-size:.9em;padding:5px}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:11}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-col-resize-guide{background-color:#999;height:100%;margin-left:-.5px;opacity:.5;position:absolute;top:0;width:4px}.tabulator .tabulator-row-resize-guide{background-color:#999;height:4px;left:0;margin-top:-.5px;opacity:.5;position:absolute;width:100%}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{background-color:#d6d6d6;color:#000}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{background-color:#3876ca;color:#fff}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;outline:none;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #151515}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #151515}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-toggle{background:#dcdcdc;border:1px solid #ccc;box-sizing:border-box;display:flex;flex-direction:row}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{background:#fff;border:1px solid #ccc;box-sizing:border-box}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;left:-3px;position:absolute;right:auto;width:6px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px} +.tabulator{background-color:#888;border:1px solid #999;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none}.tabulator .tabulator-header{background-color:#e6e6e6;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;outline:none;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#e6e6e6;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#cdcdcd;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{background-color:#d6d6d6;color:#000}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{background-color:#3876ca;color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#cdcdcd;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #191919}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;display:inline-block}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;min-width:100%;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{inset:0;pointer-events:none;position:absolute;z-index:10}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{border:1px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;position:absolute;right:-3px;width:6px}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{border:2px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-footer{background-color:#e6e6e6;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:none;display:inline-block;font-size:.9em;padding:5px}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:11}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-col-resize-guide{background-color:#999;height:100%;margin-left:-.5px;opacity:.5;position:absolute;top:0;width:4px}.tabulator .tabulator-row-resize-guide{background-color:#999;height:4px;left:0;margin-top:-.5px;opacity:.5;position:absolute;width:100%}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{background-color:#d6d6d6;color:#000}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{background-color:#3876ca;color:#fff}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;outline:none;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #191919}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0);border:1px solid #fdfdfd;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#fff;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#fff;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#191919;border-bottom:1px solid #191919;border-right:1px solid #191919;border-top:1px solid #191919;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-toggle{background:#dcdcdc;border:1px solid #ccc;box-sizing:border-box;display:flex;flex-direction:row}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{background:#fff;border:1px solid #ccc;box-sizing:border-box}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;left:-3px;position:absolute;right:auto;width:6px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #191919}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px} /*# sourceMappingURL=tabulator.min.css.map */ \ No newline at end of file diff --git a/misc/theme-tukan/Makefile b/misc/theme-tukan/Makefile index cf339ac2d..d1cc287bd 100644 --- a/misc/theme-tukan/Makefile +++ b/misc/theme-tukan/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= theme-tukan -PLUGIN_VERSION= 1.30 +PLUGIN_VERSION= 1.31 PLUGIN_COMMENT= The tukan theme - blue/white PLUGIN_MAINTAINER= rene@team-rebellion.net PLUGIN_NO_ABI= yes diff --git a/misc/theme-tukan/src/opnsense/www/themes/tukan/build/css/opnsense-bootgrid.css b/misc/theme-tukan/src/opnsense/www/themes/tukan/build/css/opnsense-bootgrid.css index 4e997565c..bc23035f3 100644 --- a/misc/theme-tukan/src/opnsense/www/themes/tukan/build/css/opnsense-bootgrid.css +++ b/misc/theme-tukan/src/opnsense/www/themes/tukan/build/css/opnsense-bootgrid.css @@ -40,7 +40,7 @@ .tabulator-row { background-color: transparent; - color: #bac3ca; + color: #000; } .tabulator-row.tabulator-row-odd { @@ -210,11 +210,6 @@ padding-right: 20px; } -.bootgrid-footer-commands { - width: 90px; - padding-left: 8px; -} - .tabulator-tableholder::after { content: ""; display: block; diff --git a/misc/theme-vicuna/Makefile b/misc/theme-vicuna/Makefile index 80af9cb0e..7db2ab4be 100644 --- a/misc/theme-vicuna/Makefile +++ b/misc/theme-vicuna/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= theme-vicuna -PLUGIN_VERSION= 1.50 +PLUGIN_VERSION= 1.51 PLUGIN_COMMENT= The vicuna theme - blue sapphire PLUGIN_MAINTAINER= rene@team-rebellion.net PLUGIN_NO_ABI= yes diff --git a/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/opnsense-bootgrid.css b/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/opnsense-bootgrid.css index 21ce95b59..03477ce3a 100644 --- a/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/opnsense-bootgrid.css +++ b/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/opnsense-bootgrid.css @@ -210,11 +210,6 @@ padding-right: 20px; } -.bootgrid-footer-commands { - width: 90px; - padding-left: 8px; -} - .tabulator-tableholder::after { content: ""; display: block; diff --git a/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/tabulator.min.css b/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/tabulator.min.css index 7034e9655..aeff5d2d8 100644 --- a/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/tabulator.min.css +++ b/misc/theme-vicuna/src/opnsense/www/themes/vicuna/build/css/tabulator.min.css @@ -1,2 +1,2 @@ -.tabulator{background-color:#888;border:1px solid #191919;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none; }.tabulator .tabulator-header{background-color:#e6e6e6;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;outline:none;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#e6e6e6;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#cdcdcd;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{background-color:#d6d6d6;color:#000}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{background-color:#3876ca;color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#cdcdcd;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #151515}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #151515}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;display:inline-block}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;min-width:100%;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{inset:0;pointer-events:none;position:absolute;z-index:10}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{border:1px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;position:absolute;right:-3px;width:6px}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{border:2px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-footer{background-color:#e6e6e6;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:none;display:inline-block;font-size:.9em;padding:5px}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:11}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-col-resize-guide{background-color:#999;height:100%;margin-left:-.5px;opacity:.5;position:absolute;top:0;width:4px}.tabulator .tabulator-row-resize-guide{background-color:#999;height:4px;left:0;margin-top:-.5px;opacity:.5;position:absolute;width:100%}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{background-color:#d6d6d6;color:#000}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{background-color:#3876ca;color:#fff}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;outline:none;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #151515}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #151515}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-toggle{background:#dcdcdc;border:1px solid #ccc;box-sizing:border-box;display:flex;flex-direction:row}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{background:#fff;border:1px solid #ccc;box-sizing:border-box}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;left:-3px;position:absolute;right:auto;width:6px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px} +.tabulator{background-color:#888;border:1px solid #999;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none}.tabulator .tabulator-header{background-color:#e6e6e6;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;outline:none;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#e6e6e6;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#cdcdcd;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{background-color:#d6d6d6;color:#000}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{background-color:#3876ca;color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#cdcdcd;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #191919}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;display:inline-block}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;min-width:100%;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{inset:0;pointer-events:none;position:absolute;z-index:10}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{border:1px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;position:absolute;right:-3px;width:6px}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{border:2px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-footer{background-color:#e6e6e6;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:none;display:inline-block;font-size:.9em;padding:5px}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:11}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-col-resize-guide{background-color:#999;height:100%;margin-left:-.5px;opacity:.5;position:absolute;top:0;width:4px}.tabulator .tabulator-row-resize-guide{background-color:#999;height:4px;left:0;margin-top:-.5px;opacity:.5;position:absolute;width:100%}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{background-color:#d6d6d6;color:#000}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{background-color:#3876ca;color:#fff}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;outline:none;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #191919}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0);border:1px solid #fdfdfd;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#fff;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#fff;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#191919;border-bottom:1px solid #191919;border-right:1px solid #191919;border-top:1px solid #191919;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-toggle{background:#dcdcdc;border:1px solid #ccc;box-sizing:border-box;display:flex;flex-direction:row}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{background:#fff;border:1px solid #ccc;box-sizing:border-box}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;left:-3px;position:absolute;right:auto;width:6px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #191919}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px} /*# sourceMappingURL=tabulator.min.css.map */ \ No newline at end of file From 5c705233079d08a468b9123518fe397d32a14ab9 Mon Sep 17 00:00:00 2001 From: 7Knights Date: Fri, 20 Feb 2026 08:36:40 -0500 Subject: [PATCH 25/87] Fix the os-redis service page status issue (#5241) --- .../opnsense/mvc/app/views/OPNsense/Redis/index.volt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt b/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt index bf0bc4d96..e031f9486 100644 --- a/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt +++ b/databases/redis/src/opnsense/mvc/app/views/OPNsense/Redis/index.volt @@ -34,12 +34,11 @@ $( document ).ready(function() { mapDataToFormUI(data_get_map).done(function(){ formatTokenizersUI(); $('.selectpicker').selectpicker('refresh'); - // request service status on load and update status box - ajaxCall(url="/api/redis/service/status", sendData={}, callback=function(data,status) { - updateServiceStatusUI(data['status']); - }); }); + // request service status on load and update status box + updateServiceControlUI('redis'); + // update history on tab state and implement navigation if(window.location.hash != "") { $('a[href="' + window.location.hash + '"]').click() @@ -73,9 +72,7 @@ $( document ).ready(function() { draggable: true }); } else { - ajaxCall(url="/api/redis/service/status", sendData={}, callback=function(data,status) { - updateServiceStatusUI(data['status']); - }); + updateServiceControlUI('redis'); } }); }); From a6acffd7fe2038492aa55ecadce2168a09f0f0b6 Mon Sep 17 00:00:00 2001 From: Monviech <79600909+Monviech@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:57:53 +0100 Subject: [PATCH 26/87] Update contributing with a small section about new plugins (#5231) --- CONTRIBUTING.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5bd849c95..b35353f5e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,3 +47,16 @@ When creating pull request, please heed the following: * Code review may ensue in order to help shape your proposal * Pull request must adhere to 2-Clause BSD licensing * Explain the problem and your proposed solution + +New plugins +----------- + +The pull request notes apply, but with the following additional points: + +* Open an issue first to explain what you want to work on and give it time for discussion +* If you are integrating a service binary it should at least be available in FreeBSD ports +* Precompiled binaries in the plugins are not allowed +* Plugins should almost always focus on integrating an existing service and providing MVC/API GUI pages for it +* It is not possible to review and integrate plugins with a large initial codebase +* If you use AI tools in your submission please disclose their use (name and model) +* Even though you are the maintainer you effectively force burden of maintainership to the community and OPNsense developers as soon as you open your first PR From 11764a1dc451dd949bf7e4140164c15d9c9ee757 Mon Sep 17 00:00:00 2001 From: Sam Sheridan Date: Tue, 24 Feb 2026 14:25:50 +0000 Subject: [PATCH 27/87] security/tailscale: Set auth key to optional in UI (#5065) --- security/tailscale/Makefile | 2 +- security/tailscale/pkg-descr | 4 ++++ .../controllers/OPNsense/Tailscale/forms/authentication.xml | 3 ++- .../mvc/app/models/OPNsense/Tailscale/Authentication.xml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/security/tailscale/Makefile b/security/tailscale/Makefile index 6c95b7cd0..67a3f4be5 100644 --- a/security/tailscale/Makefile +++ b/security/tailscale/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= tailscale -PLUGIN_VERSION= 1.3 +PLUGIN_VERSION= 1.4 PLUGIN_COMMENT= VPN mesh securely connecting clients using WireGuard PLUGIN_DEPENDS= tailscale PLUGIN_MAINTAINER= sam@sheridan.uk diff --git a/security/tailscale/pkg-descr b/security/tailscale/pkg-descr index b2066fa79..2676046f4 100644 --- a/security/tailscale/pkg-descr +++ b/security/tailscale/pkg-descr @@ -6,6 +6,10 @@ https://tailscale.com/ Plugin Changelog ================ +1.4 + +* set pre-auth key field in UI to optional, enables registration via pre-auth key or AuthURL from status page + 1.3 * modify RC script to prevent re-using auth key if already authenticated diff --git a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml index b2bcae8b3..2a0c126e5 100644 --- a/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml +++ b/security/tailscale/src/opnsense/mvc/app/controllers/OPNsense/Tailscale/forms/authentication.xml @@ -9,6 +9,7 @@ authentication.preAuthKey text - Use a non-reusable auth key and disable expiration + Use a non-reusable auth key and disable expiration (optional). If not specified use AuthURL from Status page + true diff --git a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml index b1b404f48..dce55a679 100644 --- a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml +++ b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml @@ -8,7 +8,7 @@ Please enter a valid URL - Y + N From cb77c1e6168d6bd4f2c2e212905cc35ab607cc1a Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Tue, 24 Feb 2026 15:55:19 +0100 Subject: [PATCH 28/87] security/tailscale: model changes, default validation message is enough --- .../mvc/app/models/OPNsense/Tailscale/Authentication.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml index dce55a679..0c44860cd 100644 --- a/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml +++ b/security/tailscale/src/opnsense/mvc/app/models/OPNsense/Tailscale/Authentication.xml @@ -5,10 +5,7 @@ https://controlplane.tailscale.com Y - Please enter a valid URL - - N - + From 63fc5442abb244de63113d847de9aa4cc1f45eb0 Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Tue, 24 Feb 2026 16:00:57 +0100 Subject: [PATCH 29/87] security/netbird: Add SSH configuration options (#5113) --- security/netbird/Makefile | 2 +- .../OPNsense/Netbird/forms/settings.xml | 30 +++++++++++++++++++ .../app/models/OPNsense/Netbird/Settings.php | 5 ++++ .../app/models/OPNsense/Netbird/Settings.xml | 22 +++++++++++++- 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/security/netbird/Makefile b/security/netbird/Makefile index 94edc0d4f..f7c2e956f 100644 --- a/security/netbird/Makefile +++ b/security/netbird/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= netbird -PLUGIN_VERSION= 1.1 +PLUGIN_VERSION= 1.2 PLUGIN_DEPENDS= netbird PLUGIN_COMMENT= Peer-to-peer VPN that seamlessly connects your devices PLUGIN_MAINTAINER= dev@netbird.io diff --git a/security/netbird/src/opnsense/mvc/app/controllers/OPNsense/Netbird/forms/settings.xml b/security/netbird/src/opnsense/mvc/app/controllers/OPNsense/Netbird/forms/settings.xml index 2f5f4224f..7a2cb69c7 100644 --- a/security/netbird/src/opnsense/mvc/app/controllers/OPNsense/Netbird/forms/settings.xml +++ b/security/netbird/src/opnsense/mvc/app/controllers/OPNsense/Netbird/forms/settings.xml @@ -42,6 +42,36 @@ checkbox Allows incoming SSH connections + + settings.ssh.enableRoot + + checkbox + Allow root user login + + + settings.ssh.enableSFTP + + checkbox + Enable SFTP subsystem for file transfers + + + settings.ssh.enableLocalPortForwarding + + checkbox + Allow clients to forward local ports through the server + + + settings.ssh.enableRemotePortForwarding + + checkbox + Allow clients to request remote port forwarding + + + settings.ssh.enableAuth + + checkbox + Enable JWT authentication for SSH connections. When disabled, allows any peer with network access + header diff --git a/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.php b/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.php index 0de5371db..4d3a3b4c4 100644 --- a/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.php +++ b/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.php @@ -46,6 +46,11 @@ class Settings extends BaseModel $config["WgPort"] = (int)$this->general->wireguardPort->__toString(); $config["ServerSSHAllowed"] = $this->ssh->enable->__toString() == 1; + $config["EnableSSHRoot"] = $this->ssh->enableRoot->__toString() == 1; + $config["EnableSSHSFTP"] = $this->ssh->enableSFTP->__toString() == 1; + $config["EnableSSHLocalPortForwarding"] = $this->ssh->enableLocalPortForwarding->__toString() == 1; + $config["EnableSSHRemotePortForwarding"] = $this->ssh->enableRemotePortForwarding->__toString() == 1; + $config["DisableSSHAuth"] = $this->ssh->enableAuth->__toString() != 1; $config["DisableFirewall"] = $this->firewall->allowConfig->__toString() != 1; $config["BlockInbound"] = $this->firewall->blockInboundConnection->__toString() == 1; $config["DisableDNS"] = $this->dns->enable->__toString() != 1; diff --git a/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.xml b/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.xml index a99ce9985..b606c6f75 100644 --- a/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.xml +++ b/security/netbird/src/opnsense/mvc/app/models/OPNsense/Netbird/Settings.xml @@ -1,7 +1,7 @@ //OPNsense/netbird/settings NetBird settings - 1.1.0 + 1.2.0 @@ -31,6 +31,26 @@ 0 Y + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 0 + Y + + + 1 + Y + From 24c4f9a11150cbbbd607f2a01ff0d2461e4e5f5d Mon Sep 17 00:00:00 2001 From: Nuadh123 Date: Tue, 24 Feb 2026 16:05:27 +0100 Subject: [PATCH 30/87] os-nextcloud-backup Add option to upload to one file each day instead of syncing the contents of /conf/backup/ (#5200) --- .../app/library/OPNsense/Backup/Nextcloud.php | 40 ++++++++++++++++++- .../OPNsense/Backup/NextcloudSettings.xml | 6 ++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php index e606c8516..438840c6c 100644 --- a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php +++ b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php @@ -85,13 +85,19 @@ class Nextcloud extends Base implements IBackupProvider "label" => gettext("Directory Name without leading slash, starting from user's root"), "value" => 'OPNsense-Backup' ), + array( + "name" => "strategy", + "type" => "checkbox", + "help" => gettext("Select this one to back up to a file named config-YYYYMMDD instead of syncing contents of /conf/backup"), + "label" => gettext("Daily file instead of sync all"), + ), array( "name" => "addhostname", "type" => "checkbox", "label" => gettext("Backup to directory named after hostname"), "help" => gettext("Create subdirectory under backupdir for this host"), "value" => null - ) + ), ); $nextcloud = new NextcloudSettings(); foreach ($fields as &$field) { @@ -145,6 +151,9 @@ class Nextcloud extends Base implements IBackupProvider $password = (string)$nextcloud->password; $backupdir = (string)$nextcloud->backupdir; $crypto_password = (string)$nextcloud->password_encryption; + $strategy = (string)$nextcloud->strategy; + // Strategy 0 = Sync /conf/backup + // Strategy 1 = Copy /conf/config.xml to $backupdir/conf-YYYYMMDD.xml if (!$nextcloud->addhostname->isEmpty()) { $backupdir .= "/".gethostname()."/"; @@ -158,6 +167,35 @@ class Nextcloud extends Base implements IBackupProvider return array(); } + // Backup strategy 1, sync /conf/config.xml to $backupdir/config-YYYYMMDD.xml + if ($strategy) { + $confdata = file_get_contents('/conf/config.xml'); + $mdate = filemtime('/conf/config.xml'); + $datestring = date('Ymd', $mdate); + $target_filename = 'config-' . $datestring . '.xml'; + // Optionally encrypt + if (!empty($crypto_password)) { + $confdata = $this->encrypt($confdata, $crypto_password); + } + try { + $this->upload_file_content( + $url, + $username, + $password, + $internal_username, + $backupdir, + $target_filename, + $confdata + ); + return array($backupdir . '/' . $target_filename); + } catch (\Exception $e) { + syslog(LOG_ERR, $e); + return array(); + } + } + + // Default strategy (0), sync /conf/backup/ + // Get list of files from local backup system $local_files = array(); $tmp_local_files = scandir('/conf/backup/'); diff --git a/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml b/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml index e5c5b10c3..e18c69d5c 100644 --- a/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml +++ b/sysutils/nextcloud-backup/src/opnsense/mvc/app/models/OPNsense/Backup/NextcloudSettings.xml @@ -1,6 +1,6 @@ //system/backup/nextcloud - 1.0.1 + 1.0.2 OPNsense Nextcloud Backup Settings @@ -49,6 +49,10 @@ OPNsense-Backup The Backup Directory can only consist of alphanumeric characters, dash, underscores and slash. No leading or trailing slash. + + Y + 0 + 1 Y From 10c98b7b09538a258040b1dba4d235c680ed65dc Mon Sep 17 00:00:00 2001 From: Nuadh123 Date: Tue, 24 Feb 2026 19:32:43 +0100 Subject: [PATCH 31/87] os-nextcloud-backup Only back up when local file is newer than remote (#5213) --- .../app/library/OPNsense/Backup/Nextcloud.php | 87 +++++++++++++++++-- 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php index 438840c6c..58956a44c 100644 --- a/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php +++ b/sysutils/nextcloud-backup/src/opnsense/mvc/app/library/OPNsense/Backup/Nextcloud.php @@ -134,6 +134,41 @@ class Nextcloud extends Base implements IBackupProvider return $validation_messages; } + /** + * check remote file last modified tag + * @param string $remote_filename filename to check on server + * @param string $username username for login to server + * @param string $password password for authentication + * @return int unix timestamp or 0 if errors occour + */ + public function get_remote_file_lastmodified( + $remote_filename, + $username, + $password + ) { + $reply = $this->curl_request_nothrow($remote_filename, $username, $password, 'PROPFIND', 'Cannot get remote fileinfo'); + $http_code = $reply['info']['http_code']; + if ($http_code >= 200 && $http_code < 300) { + $xml_data = $reply['response']; + if ($xml_data == NULL) { + syslog(LOG_ERR, 'Data was NULL'); + return 0; + } + $xml_data = str_replace(['children() as $response) { + if ($response->getName() == 'response') { + $lastmodifiedstr = $response->propstat->prop->getlastmodified; + $filedate = strtotime($lastmodifiedstr); + return $filedate; + } + } + } + return 0; + } + + + /** * perform backup * @return array filelist @@ -173,10 +208,17 @@ class Nextcloud extends Base implements IBackupProvider $mdate = filemtime('/conf/config.xml'); $datestring = date('Ymd', $mdate); $target_filename = 'config-' . $datestring . '.xml'; + // Find the same filename @ remote + $remote_filename = $url . '/remote.php/dav/files/' . $internal_username . '/' . $backupdir . '/' . $target_filename; + $remote_file_date = $this->get_remote_file_lastmodified($remote_filename, $username, $password); + if ($remote_file_date >= $mdate) { + return array(); + } // Optionally encrypt if (!empty($crypto_password)) { $confdata = $this->encrypt($confdata, $crypto_password); } + // Finally, upload some data try { $this->upload_file_content( $url, @@ -189,7 +231,7 @@ class Nextcloud extends Base implements IBackupProvider ); return array($backupdir . '/' . $target_filename); } catch (\Exception $e) { - syslog(LOG_ERR, $e); + syslog(LOG_ERR, 'Backup to ' . $url . ' failed: ' . $e); return array(); } } @@ -303,14 +345,21 @@ class Nextcloud extends Base implements IBackupProvider */ public function upload_file_content($url, $username, $password, $internal_username, $backupdir, $filename, $local_file_content) { - $this->curl_request( - $url . "/remote.php/dav/files/$internal_username/$backupdir/$filename", + $url = $url . "/remote.php/dav/files/$internal_username/$backupdir/$filename"; + $reply = $this->curl_request( + $url, $username, $password, 'PUT', 'cannot execute PUT', $local_file_content ); + $http_code = $reply['info']['http_code']; + // Accepted http codes for upload is 200-299 + if (!($http_code >= 200 && $http_code < 300)) { + syslog(LOG_ERR, 'Could not PUT '. $url); + throw new \Exception(); + } } /** @@ -394,6 +443,31 @@ class Nextcloud extends Base implements IBackupProvider $error_message, $postdata = null, $headers = array("User-Agent: OPNsense Firewall") + ) { + $result = $this->curl_request_nothrow($url, $username, $password, $method, $error_message, $postdata, $headers); + $info = $result['info']; + $err = $result['err']; + $response = $result['response']; + if (!($info['http_code'] == 200 || $info['http_code'] == 207 || $info['http_code'] == 201) || $err) { + syslog(LOG_ERR, $error_message); + syslog(LOG_ERR, json_encode($info)); + throw new \Exception(); + } + return array('response' => $response, 'info' => $info); + } + + + // Add this here, since I'm fundamentally opposed to throwing exceptions + // if http codes aren't to your liking in a generic function. + // Delegate that to upper functions, where it belongs. + public function curl_request_nothrow( + $url, + $username, + $password, + $method, + $error_message, + $postdata = null, + $headers = array("User-Agent: OPNsense Firewall") ) { $curl = curl_init(); curl_setopt_array($curl, array( @@ -413,13 +487,8 @@ class Nextcloud extends Base implements IBackupProvider $response = curl_exec($curl); $err = curl_error($curl); $info = curl_getinfo($curl); - if (!($info['http_code'] == 200 || $info['http_code'] == 207 || $info['http_code'] == 201) || $err) { - syslog(LOG_ERR, $error_message); - syslog(LOG_ERR, json_encode($info)); - throw new \Exception(); - } curl_close($curl); - return array('response' => $response, 'info' => $info); + return array('response' => $response, 'info' => $info, 'err' => $err); } /** From 2bf6206e0136495a8e5dc1ef9e666be27f046391 Mon Sep 17 00:00:00 2001 From: Self-Hosting-Group <155233284+Self-Hosting-Group@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:52:43 +0100 Subject: [PATCH 32/87] net/upnp: Complete service improvements (#5126) --- net/upnp/Makefile | 2 +- .../src/etc/inc/plugins.inc.d/miniupnpd.inc | 19 ++++++++-- .../mvc/app/models/OPNsense/UPnP/ACL/ACL.xml | 7 ++++ .../app/models/OPNsense/UPnP/Menu/Menu.xml | 1 + .../OPNsense/Syslog/local/miniupnpd.conf | 6 ++++ net/upnp/src/www/services_upnp.php | 35 ++++++++++++++----- net/upnp/src/www/status_upnp.php | 8 ++--- 7 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 net/upnp/src/opnsense/service/templates/OPNsense/Syslog/local/miniupnpd.conf diff --git a/net/upnp/Makefile b/net/upnp/Makefile index 4c5c4109b..a805a22bb 100644 --- a/net/upnp/Makefile +++ b/net/upnp/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= upnp -PLUGIN_VERSION= 1.8 +PLUGIN_VERSION= 1.9 PLUGIN_DEPENDS= miniupnpd PLUGIN_COMMENT= UPnP IGD & PCP/NAT-PMP Service PLUGIN_MAINTAINER= franco@opnsense.org diff --git a/net/upnp/src/etc/inc/plugins.inc.d/miniupnpd.inc b/net/upnp/src/etc/inc/plugins.inc.d/miniupnpd.inc index 069680a36..e7ff45117 100644 --- a/net/upnp/src/etc/inc/plugins.inc.d/miniupnpd.inc +++ b/net/upnp/src/etc/inc/plugins.inc.d/miniupnpd.inc @@ -26,6 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ +function miniupnpd_syslog() +{ + return ['miniupnpd' => ['facility' => ['miniupnpd']]]; +} + function miniupnpd_enabled() { global $config; @@ -75,7 +80,16 @@ function miniupnpd_start() return; } - mwexecfb('/usr/local/sbin/miniupnpd -f %s -P %s', [ '/var/etc/miniupnpd.conf', '/var/run/miniupnpd.pid']); + global $config; + $upnp_config = $config['installedpackages']['miniupnpd']['config'][0]; + if (($upnp_config['log_level'] ?? '') == 'info') { + $exec_cmd='/usr/local/sbin/miniupnpd -f %s -P %s -v'; + } elseif (($upnp_config['log_level'] ?? '') == 'debug') { + $exec_cmd='/usr/local/sbin/miniupnpd -f %s -P %s -vv'; + } else { + $exec_cmd='/usr/local/sbin/miniupnpd -f %s -P %s'; + } + mwexecfb($exec_cmd, ['/var/etc/miniupnpd.conf', '/var/run/miniupnpd.pid']); } function miniupnpd_stop() @@ -217,7 +231,8 @@ function miniupnpd_configure_do($verbose = false) /* enable system uptime instead of miniupnpd uptime */ if (!empty($upnp_config['sysuptime'])) { - $config_text .= "system_uptime=yes\n"; + /* Disable system uptime to workaround daemon bug with PCP/NAT-PMP epoch on BSD */ + //$config_text .= "system_uptime=yes\n"; } /* set webgui url */ diff --git a/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/ACL/ACL.xml b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/ACL/ACL.xml index 23a2fa96e..77d5cf582 100644 --- a/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/ACL/ACL.xml +++ b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/ACL/ACL.xml @@ -11,4 +11,11 @@ status_upnp.php* + + Services: UPnP IGD & PCP: Log File + + ui/diagnostics/log/core/miniupnpd/* + api/diagnostics/log/core/miniupnpd/* + + diff --git a/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/Menu/Menu.xml b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/Menu/Menu.xml index c040d5ea1..4359bbcdb 100644 --- a/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/Menu/Menu.xml +++ b/net/upnp/src/opnsense/mvc/app/models/OPNsense/UPnP/Menu/Menu.xml @@ -5,6 +5,7 @@ + diff --git a/net/upnp/src/opnsense/service/templates/OPNsense/Syslog/local/miniupnpd.conf b/net/upnp/src/opnsense/service/templates/OPNsense/Syslog/local/miniupnpd.conf new file mode 100644 index 000000000..107e0a414 --- /dev/null +++ b/net/upnp/src/opnsense/service/templates/OPNsense/Syslog/local/miniupnpd.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [miniupnpd]. +################################################################### +filter f_local_miniupnpd { + program("miniupnpd"); +}; diff --git a/net/upnp/src/www/services_upnp.php b/net/upnp/src/www/services_upnp.php index ec5402042..7ac9002f7 100644 --- a/net/upnp/src/www/services_upnp.php +++ b/net/upnp/src/www/services_upnp.php @@ -78,6 +78,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { 'friendly_name', 'iface_array', 'ipv6_disable', + 'log_level', 'logpackets', 'overridesubnet', 'overridewanip', @@ -186,7 +187,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $upnp['num_permuser'] = $pconfig['num_permuser']; } // text field types - foreach (['ext_iface', 'download', 'upload', 'overridewanip', 'overridesubnet', 'stun_host', 'stun_port', 'friendly_name', 'upnp_igd_compat'] as $fieldname) { + foreach (['download', 'ext_iface', 'friendly_name', 'log_level', 'overridesubnet', 'overridewanip', 'stun_host', 'stun_port', 'upload', 'upnp_igd_compat'] as $fieldname) { $upnp[$fieldname] = $pconfig[$fieldname]; } foreach (miniupnpd_permuser_list() as $fieldname) { @@ -233,16 +234,16 @@ include("head.inc"); - + /> - + />