unbound: move policy fetch to the controller, clean up accordingly. Fixes https://github.com/opnsense/core/issues/9814

This commit is contained in:
Stephan de Wit 2026-02-18 16:45:19 +01:00
parent ff41dee988
commit 00b5984025
2 changed files with 113 additions and 134 deletions

View file

@ -36,23 +36,10 @@ use OPNsense\Firewall\Util;
class OverviewController extends ApiControllerBase
{
private $mdl = null;
private $types = [];
private $nodes = [];
public function __construct()
{
$this->mdl = new \OPNsense\Unbound\Unbound();
$this->types = $this->mdl->dnsbl->blocklist->getTemplateNode()->type->getNodeData();
$this->nodes = $this->mdl->dnsbl->blocklist->getNodes();
}
private function getBlocklistDescription($shortcode)
{
if (array_key_exists($shortcode, $this->types)) {
return $this->types[$shortcode]['value'];
}
return null;
}
public function isEnabledAction()
@ -65,7 +52,8 @@ class OverviewController extends ApiControllerBase
/* XXX deprecated and to be removed for 26.7 */
public function isBlockListEnabledAction()
{
return ['enabled' => (bool)array_filter($this->nodes, fn($v) => $v['enabled'])];
$nodes = $this->mdl->dnsbl->blocklist->getNodes();
return ['enabled' => (bool)array_filter($nodes, fn($v) => $v['enabled'])];
}
public function RollingAction($timeperiod, $clients = '0')
@ -108,10 +96,12 @@ class OverviewController extends ApiControllerBase
}
$parsed = json_decode($response, true) ?? [];
$policies = $this->getPoliciesAction();
$types = $this->mdl->dnsbl->blocklist->getTemplateNode()->type->getNodeData();
foreach ($parsed as $idx => $query) {
$parsed[$idx]['blocklist'] ??= $this->getBlocklistDescription($query['blocklist']);
$parsed[$idx]['blocklist'] = $types[$query['blocklist']]['value'] ?? $query['blocklist'];
$parsed[$idx]['policy'] = $policies[$query['uuid']]['description'] ?? '';
/* Handle front-end color status mapping, start off with OK */
$parsed[$idx]['status'] = 0;
@ -133,21 +123,14 @@ class OverviewController extends ApiControllerBase
return $response;
}
public function getPoliciesAction($uuid = null)
public function getPoliciesAction()
{
$response = ['policies' => []];
$response = [];
foreach ($this->mdl->dnsbl->blocklist->iterateItems() as $policy_uuid => $policy) {
if ($uuid !== null && $uuid == $policy_uuid) {
$response = $policy->getNodeContent();
$response['uuid'] = $policy_uuid;
break;
}
$response['policies'][$policy_uuid] = $policy->getNodeContent();
$response[$policy_uuid] = $policy->getNodeContent();
}
$response['blocklistDescriptions'] = $this->types;
return $response;
}
}

View file

@ -381,7 +381,7 @@
function createTopList(id, data, type, maxDomains = 10) {
ajaxGet('/api/unbound/overview/get_policies', {}, function(policies, status) {
const enabled = Object.values(policies.policies).some(v => v.enabled === "1");
const enabled = Object.values(policies).some(v => v.enabled === "1");
for (let i = 0; i < maxDomains; i++) {
let category = type === 'block' ? data.top_blocked : data.top;
let domain = Object.keys(category)[i];
@ -411,7 +411,7 @@
openPoliciesDialog(domain, uuid, action, statObj?.blocklist ?? "");
});
let bl = (uuid && uuid in policies.policies) ? `(${policies.policies[uuid].description})` : '';
let bl = (uuid && uuid in policies) ? `(${policies[uuid].description})` : '';
let $li = $(`
<li class="list-group-item list-group-item-border list-item-domain top-item">
@ -564,20 +564,29 @@
const cleanDomain = domain.replace(/\.$/, "");
ajaxGet('/api/unbound/overview/get_policies', {}, function (data, status) {
let $container = $('<div>');
const enabled = Object.values(data).some(v => v.enabled === "1");
if (!enabled) {
$container.html(`
{{ lang._('There are no blocklist policies defined. You can create them %shere%s') | format('<a href="/ui/unbound/dnsbl/index#blocklists">', '</a>') }}
`);
dialogRef.setMessage($container);
return;
}
const $table = $('<table class="table table-striped table-sm mb-0">');
const $tbody = $('<tbody>');
const $thead = $('<thead>').append(
$('<tr>').append(
$('<th>').text("{{ lang._('Blocklist') }}"),
$('<th>').text("{{ lang._('Policy') }}"),
$('<th>').text("{{ lang._('Source net(s)') }}"),
$('<th>').text("{{ lang._('Action') }}")
)
);
$table.append($thead, $tbody);
const descriptions = data['blocklistDescriptions'];
for (const [policy_uuid, policy] of Object.entries(data.policies)) {
for (const [policy_uuid, policy] of Object.entries(data)) {
if (policy.enabled == '0') continue;
const description =
@ -618,12 +627,14 @@
$tbody.append($tr);
}
const blocklistMatch = descriptions[blocklist]?.value ?? blocklist;
$container = $(`
<div>
{{ lang._('Blocklist match:')}} ${blocklistMatch}
</div>
`);
if (blocklist != "") {
$container = $(`
<div>
{{ lang._('Blocklist match:')}} ${blocklist}
</div>
`);
}
$container.append($table);
dialogRef.setMessage($container);
@ -663,108 +674,92 @@
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
if (e.target.id == 'query_details_tab') {
$("#grid-queries").bootgrid('destroy');
ajaxGet('/api/unbound/overview/get_policies', {}, function(policies, status) {
const enabled = Object.values(policies.policies).some(v => v.enabled === "1");
let grid_queries = $("#grid-queries").UIBootgrid({
search:'/api/unbound/overview/search_queries/',
commands: {
action: {
title: "{{ lang._('Quick action') }}",
classname: 'fa fa-cogs',
sequence: 100,
filter: function(cell) {
const data = cell.getData();
return enabled && (data.action == 'Pass' || data.action == 'Block');
},
onRendered: function(cell) {
const $el = $(this);
const data = cell.getData();
const uuid = data.uuid;
const domain = data.domain;
const appliedAction = data.action;
const blocklist = data.blocklist;
let grid_queries = $("#grid-queries").UIBootgrid({
search:'/api/unbound/overview/search_queries/',
commands: {
action: {
title: "{{ lang._('Quick action') }}",
classname: 'fa fa-cogs',
sequence: 100,
filter: function(cell) {
const data = cell.getData();
return data.action == 'Pass' || data.action == 'Block';
},
onRendered: function(cell) {
const $el = $(this);
const data = cell.getData();
const uuid = data.uuid;
const domain = data.domain;
const appliedAction = data.action;
const blocklist = data.blocklist;
$el.click(function() {
openPoliciesDialog(domain, uuid, appliedAction, blocklist);
});
}
$el.click(function() {
openPoliciesDialog(domain, uuid, appliedAction, blocklist);
});
}
}
},
options: {
virtualDOM: true,
rowSelect: false,
multiSelect: false,
selection: false,
useRequestHandlerOnGet: true,
requestHandler: function(request) {
if (g_clientFilter != null && g_timeFilter != null) {
let timestamp = g_timeFilter / 1000;
let interval = $("#timeperiod-clients").val() == 1 ? 60 : 600;
request['client'] = g_clientFilter;
request['timeStart'] = timestamp;
request['timeEnd'] = timestamp + interval;
}
return request;
},
formatters: {
"timeformatter": function (column, row) {
return moment.unix(row.time).local().format('YYYY-MM-DD HH:mm:ss');
},
"resolveformatter": function (column, row) {
return row.resolve_time_ms + 'ms';
},
"domain": function (column, row) {
return row.domain;
}
},
options: {
virtualDOM: true,
rowSelect: false,
multiSelect: false,
selection: false,
useRequestHandlerOnGet: true,
requestHandler: function(request) {
if (g_clientFilter != null && g_timeFilter != null) {
let timestamp = g_timeFilter / 1000;
let interval = $("#timeperiod-clients").val() == 1 ? 60 : 600;
request['client'] = g_clientFilter;
request['timeStart'] = timestamp;
request['timeEnd'] = timestamp + interval;
}
return request;
},
formatters: {
"timeformatter": function (column, row) {
return moment.unix(row.time).local().format('YYYY-MM-DD HH:mm:ss');
},
"resolveformatter": function (column, row) {
return row.resolve_time_ms + 'ms';
},
"domain": function (column, row) {
return row.domain;
},
"blocklist": function (column, row) {
if (row.uuid && policies.policies[row.uuid]) {
return policies.policies[row.uuid].description;
}
return '';
}
},
statusMapping: {
0: "query-success",
1: "query-info",
2: "query-warning",
3: "query-danger",
4: "query-error"
}
statusMapping: {
0: "query-success",
1: "query-info",
2: "query-warning",
3: "query-danger",
4: "query-error"
}
}).on("loaded.rs.jquery.bootgrid", function (e) {
if (g_clientFilter != null && g_timeFilter != null && !$('#searchFilter').length) {
// Add a badge to signify we're in a drill-down
let label = (typeof g_labelFilter != 'undefined') ? g_labelFilter : g_clientFilter;
$('div.actionBar').prepend($('<div id="searchFilter"></div>'));
let timeStart = moment.unix(g_timeFilter / 1000).local().format('MM-DD HH:mm');
let interval = $("#timeperiod-clients").val() == 1 ? 60 : 600;
let timeEnd = moment.unix((g_timeFilter / 1000) + interval).local().format('MM-DD HH:mm');
$('#searchFilter').append('<span class="tag badge badge-pill badge-secondary">' +
label + ' (' + timeStart + ' - ' + timeEnd + ')' +
'<a id="removeFilter"><i class="fa fa-times" aria-hidden="true"></i></span></a>');
}
}).on("loaded.rs.jquery.bootgrid", function (e) {
if (g_clientFilter != null && g_timeFilter != null && !$('#searchFilter').length) {
// Add a badge to signify we're in a drill-down
let label = (typeof g_labelFilter != 'undefined') ? g_labelFilter : g_clientFilter;
$('div.actionBar').prepend($('<div id="searchFilter"></div>'));
let timeStart = moment.unix(g_timeFilter / 1000).local().format('MM-DD HH:mm');
let interval = $("#timeperiod-clients").val() == 1 ? 60 : 600;
let timeEnd = moment.unix((g_timeFilter / 1000) + interval).local().format('MM-DD HH:mm');
$('#searchFilter').append('<span class="tag badge badge-pill badge-secondary">' +
label + ' (' + timeStart + ' - ' + timeEnd + ')' +
'<a id="removeFilter"><i class="fa fa-times" aria-hidden="true"></i></span></a>');
$('#removeFilter').click(function(e) {
// Reset filters set by a client drill-down
g_clientFilter = null;
g_timeFilter = null;
g_labelFilter = null;
$('#searchFilter').remove();
$('#grid-queries').bootgrid('reload');
})
}
if (!enabled) {
$(".hide-col").css("display", "none");
} else {
$(".hide-col").css('display', '');
}
$(".domain-content").tooltip({placement: "auto left"});
});
})
$('#removeFilter').click(function(e) {
// Reset filters set by a client drill-down
g_clientFilter = null;
g_timeFilter = null;
g_labelFilter = null;
$('#searchFilter').remove();
$('#grid-queries').bootgrid('reload');
})
}
$(".domain-content").tooltip({placement: "auto left"});
});
}
if (e.target.id == 'query_overview_tab') {
// Reset filters set by a client drill-down
@ -963,6 +958,7 @@
<th data-column-id="resolve_time_ms" data-type="string" data-formatter="resolveformatter">{{ lang._('Resolve time') }}</th>
<th data-column-id="ttl" data-width="6em" data-type="string">{{ lang._('TTL') }}</th>
<th data-column-id="blocklist" data-type="string" data-formatter="blocklist">{{ lang._('Blocklist') }}</th>
<th data-column-id="policy" data-type="string">{{ lang._('Policy') }}</th>
<th data-column-id="" data-width="100" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>