Reporting: Unbound DNS: Tweak UI and usability improvements

- Increase the reporting interval for 12 & 24 hours to 10 minutes.
- Always display a list for the top (blocked) domains
- Remove block/whitelist buttons when blocklists are disabled
This commit is contained in:
Stephan de Wit 2023-01-03 13:44:58 +01:00
parent c3f4183171
commit 4a5406424f
5 changed files with 113 additions and 74 deletions

View File

@ -43,11 +43,18 @@ class OverviewController extends ApiControllerBase
];
}
public function isBlockListEnabledAction()
{
return [
'enabled' => (new \OPNsense\Unbound\Unbound())->getNodes()['dnsbl']['enabled']
];
}
public function RollingAction($timeperiod, $clients = false)
{
$this->sessionClose();
// Sanitize input
$interval = preg_replace("/^(?:(?!1|12|24).)*$/", "24", $timeperiod) == 1 ? 60 : 300;
$interval = preg_replace("/^(?:(?!1|12|24).)*$/", "24", $timeperiod) == 1 ? 60 : 600;
$type = $clients ? 'clients' : 'rolling';
$response = (new Backend())->configdpRun('unbound qstats ' . $type, [$interval, $timeperiod]);
return json_decode($response, true);

View File

@ -253,7 +253,7 @@
/* Add a redundant data step to end the chart time axis properly */
if (formatted.length > 0) {
let lastVal = formatted[formatted.length - 1];
let interval = $("#timeperiod").val() == 1 ? 60 : 300;
let interval = $("#timeperiod").val() == 1 ? 60 : 600;
let y_val = logarithmic ? 0.1 : 0
formatted.push({
x: lastVal.x + (interval * 1000),
@ -290,7 +290,7 @@
/* Add a redundant data step to end the chart time axis properly */
if (tmp.length > 0) {
let lastVal = tmp[tmp.length - 1];
let interval = $("#timeperiod-clients").val() == 1 ? 60 : 300;
let interval = $("#timeperiod-clients").val() == 1 ? 60 : 600;
tmp.push({
x: lastVal.x + (interval * 1000),
y: backup_val,
@ -354,23 +354,41 @@
}
function createTopList(id, data, type) {
let idx = 1;
for (const [domain, statObj] of Object.entries(data)) {
ajaxGet('/api/unbound/overview/isBlockListEnabled', {}, function(bl_enabled, status) {
let class_type = type == "pass" ? "block-domain" : "whitelist-domain";
let icon_type = type == "pass" ? "fa fa-ban text-danger" : "fa fa-pencil text-info";
let bl = statObj.hasOwnProperty('blocklist') ? '(' + statObj.blocklist + ')' : '';
$('#' + id).append(
'<li class="list-group-item list-group-item-border top-item">' +
idx + '. ' + domain + ' ' + bl +
'<span class="counter">'+ statObj.total +' (' + statObj.pcnt +'%)' +
'<button class="'+ class_type + '" data-value="'+ domain +'" ' +
'data-toggle="tooltip" style="margin-left: 10px;"><i class="' + icon_type + '"></i></button>' +
'</span></li>'
)
idx++;
}
$(".block-domain").tooltip().attr('title', "{{ lang._('Block Domain') }}");
$(".whitelist-domain").tooltip().attr('title', "{{ lang._('Whitelist Domain') }}");
for (let i = 0; i < 10; i++) {
let domain = Object.keys(data)[i];
let statObj = Object.values(data)[i];
if (typeof domain == 'undefined' || typeof statObj == 'undefined') {
$('#' + id).append(
'<li class="list-group-item list-group-item-border top-item">' +
(i + 1) + '. ' +
'<span class="counter">0 (0.0%)' +
'</span></li>'
)
continue;
}
let icon = '<button class="'+ class_type + '" data-value="'+ domain +'" ' +
'data-toggle="tooltip" style="margin-left: 10px;"><i class="' + icon_type + '"></i></button>'
if (bl_enabled.enabled == 0) {
icon = '';
}
let bl = statObj.hasOwnProperty('blocklist') ? '(' + statObj.blocklist + ')' : '';
$('#' + id).append(
'<li class="list-group-item list-group-item-border top-item">' +
(i + 1) + '. ' + domain + ' ' + bl +
'<span class="counter">'+ statObj.total +' (' + statObj.pcnt +'%)' +
icon +
'</span></li>'
)
}
$(".block-domain").tooltip().attr('title', "{{ lang._('Block Domain') }}");
$(".whitelist-domain").tooltip().attr('title', "{{ lang._('Whitelist Domain') }}");
});
}
function create_or_update_totals() {
@ -478,62 +496,72 @@
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
if (e.target.id == 'query_details_tab') {
$("#grid-queries").bootgrid('destroy');
let grid_queries = $("#grid-queries").UIBootgrid({
search:'/api/unbound/overview/searchQueries/',
options: {
rowSelect: false,
multiSelect: false,
selection: false,
formatters: {
"timeformatter": function (column, row) {
return moment.unix(row.time).local().format('YYYY-MM-DD HH:mm:ss');
ajaxGet('/api/unbound/overview/isBlockListEnabled', {}, function(bl_enabled, status) {
let grid_queries = $("#grid-queries").UIBootgrid({
search:'/api/unbound/overview/searchQueries/',
options: {
rowSelect: false,
multiSelect: false,
selection: false,
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';
},
"commands": function (column, row) {
let btn = '';
if (bl_enabled.enabled == 0) {
return btn;
} else if (row.action == 'Pass') {
btn = '<button type="button" class="btn-secondary block-domain" data-domain=' + row.domain + ' data-toggle="tooltip"><i class="fa fa-ban text-danger"></i></button> ';
} else if (row.action == 'Block') {
btn = '<button type="button" class="btn-secondary whitelist-domain" data-domain=' + row.domain + ' data-toggle="tooltip"><i class="fa fa-pencil text-info"></i></button>';
}
return btn;
},
},
"resolveformatter": function (column, row) {
return row.resolve_time_ms + 'ms';
},
"commands": function (column, row) {
let btn = '';
if (row.action == 'Pass') {
btn = '<button type="button" class="btn-secondary block-domain" data-domain=' + row.domain + ' data-toggle="tooltip"><i class="fa fa-ban text-danger"></i></button> ';
} else if (row.action == 'Block') {
btn = '<button type="button" class="btn-secondary whitelist-domain" data-domain=' + row.domain + ' data-toggle="tooltip"><i class="fa fa-pencil text-info"></i></button>';
}
return btn;
},
},
statusMapping: {
0: "query-success",
1: "info",
2: "query-warning",
3: "query-danger",
4: "danger"
statusMapping: {
0: "query-success",
1: "info",
2: "query-warning",
3: "query-danger",
4: "danger"
}
}
}
}).on("loaded.rs.jquery.bootgrid", function (e) {
$(".block-domain").tooltip().attr('title', "{{ lang._('Block Domain') }}");
$(".whitelist-domain").tooltip().attr('title', "{{ lang._('Whitelist Domain') }}");
grid_queries.find(".block-domain").on("click", function(e) {
$(this).remove("i").html('<i class="fa fa-spinner fa-spin"></i>');
ajaxCall('/api/unbound/settings/updateBlocklist', {
'domain': $(this).data('domain'),
'type': 'blocklists',
}, function(data, status) {
let btn = grid_queries.find(".block-domain");
btn.remove("i").html('<i class="fa fa-ban text-danger"></i>');
});
});
}).on("loaded.rs.jquery.bootgrid", function (e) {
if (bl_enabled.enabled == 0) {
$(".hide-col").css("display", "none");
} else {
$(".hide-col").css('display', '');
}
$(".block-domain").tooltip().attr('title', "{{ lang._('Block Domain') }}");
$(".whitelist-domain").tooltip().attr('title', "{{ lang._('Whitelist Domain') }}");
grid_queries.find(".block-domain").on("click", function(e) {
$(this).remove("i").html('<i class="fa fa-spinner fa-spin"></i>');
ajaxCall('/api/unbound/settings/updateBlocklist', {
'domain': $(this).data('domain'),
'type': 'blocklists',
}, function(data, status) {
let btn = grid_queries.find(".block-domain");
btn.remove("i").html('<i class="fa fa-ban text-danger"></i>');
});
});
grid_queries.find(".whitelist-domain").on("click", function(e) {
$(this).remove("i").html('<i class="fa fa-spinner fa-spin"></i>');
ajaxCall('/api/unbound/settings/updateBlocklist', {
'domain': $(this).data('domain'),
'type': 'whitelists',
}, function(data, status) {
let btn = grid_queries.find(".whitelist-domain");
btn.remove("i").html('<i class="fa fa-pencil text-info"></i>');
});
});
});
})
grid_queries.find(".whitelist-domain").on("click", function(e) {
$(this).remove("i").html('<i class="fa fa-spinner fa-spin"></i>');
ajaxCall('/api/unbound/settings/updateBlocklist', {
'domain': $(this).data('domain'),
'type': 'whitelists',
}, function(data, status) {
let btn = grid_queries.find(".whitelist-domain");
btn.remove("i").html('<i class="fa fa-pencil text-info"></i>');
});
});
});
}
if (e.target.id == 'query_overview_tab') {
create_or_update_totals();
@ -714,7 +742,7 @@
<th data-column-id="resolve_time_ms" data-type="string" data-formatter="resolveformatter">{{ lang._('Resolve time') }}</th>
<th data-column-id="ttl" data-type="string">{{ lang._('TTL') }}</th>
<th data-column-id="blocklist" data-type="string">{{ lang._('Blocklist') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Command') }}</th>
<th data-header-css-class="hide-col" data-css-class="hide-col" data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Command') }}</th>
</tr>
</thead>
<tbody>

View File

@ -68,7 +68,7 @@ class DNSReader:
)
""")
for size in [300, 60]:
for size in [600, 300, 60]:
db.connection.execute(
"""
CREATE OR REPLACE VIEW v_time_series_{min}min AS (

View File

@ -45,7 +45,7 @@ def percent(val, total):
def handle_rolling(args):
# sanitize input
interval = int(re.sub("^(?:(?!300|60).)*$", "300", str(args.interval)))
interval = int(re.sub("^(?:(?!600|300|60).)*$", "600", str(args.interval)))
tp = int(re.sub("^(?:(?!24|12|1).)*$", "24", str(args.timeperiod)))
data = pandas.DataFrame()

View File

@ -65,6 +65,10 @@
padding-bottom: 10px;
}
.list-group-item {
height: 2.5em;
}
.list-group-item-border {
border: 1px solid #ddd;
}