%PDF- %PDF-
| Direktori : /proc/thread-self/root/backups/router/usr/local/opnsense/www/js/widgets/ |
| Current File : //proc/thread-self/root/backups/router/usr/local/opnsense/www/js/widgets/Firewall.js |
/*
* Copyright (C) 2024 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
export default class Firewall extends BaseTableWidget {
constructor(config) {
super(config);
this.ifMap = {};
this.counters = {};
this.chart = null;
this.rotation = 5;
}
getMarkup() {
let $container = $('<div></div>');
let $tableContainer = $(`<div id="fw-table-container"><b>${this.translations.livelog}</b></div>`);
let $top_table = this.createTable('fw-top-table', {
headerPosition: 'top',
rotation: this.rotation,
headers: [
this.translations.action,
this.translations.time,
this.translations.interface,
this.translations.source,
this.translations.destination,
this.translations.port
],
});
let $rule_table = this.createTable('fw-rule-table', {
headerPosition: 'top',
rotation: this.rotation,
headers: [
this.translations.label,
this.translations.count
],
sortIndex: 1,
sortOrder: 'desc'
});
$tableContainer.append($top_table);
$tableContainer.append(`<div style="margin-top: 2em"><b>${this.translations.events}</b><div>`);
$tableContainer.append($rule_table);
$container.append($tableContainer);
$container.append($(`
<div class="fw-chart-container">
<div class="canvas-container">
<canvas id="fw-chart"></canvas>
</div>
</div>
`));
return $container;
}
_onMessage(event) {
if (!event) {
super.closeEventSource();
}
$('.ip-tooltip').tooltip('hide');
let actIcons = {
'pass': '<i class="fa fa-play text-success"></i>',
'block': '<i class="fa fa-minus-circle text-danger"></i>',
'rdr': '<i class="fa fa-exchange text-info"></i>',
'nat': '<i class="fa fa-exchange text-info"></i>',
}
const data = JSON.parse(event.data);
// increase counters
if (!this.counters[data.rid]) {
this.counters[data.rid] = {
count: data.counter,
label: data.label ?? ''
}
} else {
this.counters[data.rid].count = data.counter;
}
let popContent = $(`
<p>
@${data.rulenr}
${data.label.length > 0 ? 'Label: ' + data.label : ''}
<br>
<sub>${this.translations.click}</sub>
</p>
`).prop('outerHTML');
let popover = $(`
<a target="_blank" href="/ui/diagnostics/firewall/log?rid=${data.rid}" type="button"
data-toggle="popover" data-trigger="hover" data-html="true" data-title="${this.translations.matchedrule}"
data-content="${popContent}">
${actIcons[data.action]}
</a>
`);
super.updateTable('fw-top-table', [
[
popover.prop('outerHTML'),
/* Format time based on client browser locale */
(new Intl.DateTimeFormat(undefined, {hour: 'numeric', minute: 'numeric'})).format(new Date(data.__timestamp__)),
this.ifMap[data.interface] ?? data.interface,
`<span class="ip-tooltip" style="cursor: pointer; data-toggle="tooltip" title="${data.src}">${data.src}</span>`,
`<span class="ip-tooltip" style="cursor: pointer; data-toggle="tooltip" title="${data.dst}">${data.dst}</span>`,
data.dstport ?? ''
]
]);
$('.ip-tooltip').tooltip({container: 'body'});
super.updateTable('fw-rule-table', [
[
popover.html($(`<div style="text-align: left;">${this.counters[data.rid].label}</div>`)).prop('outerHTML'),
this.counters[data.rid].count
]
], data.rid);
$('[data-toggle="popover"]').popover('hide');
$('[data-toggle="popover"]').popover({
container: 'body'
}).on('show.bs.popover', function() {
$(this).data("bs.popover").tip().css("max-width", "100%")
});
this._updateChart(data.rid, this.counters[data.rid].label, this.counters[data.rid].count);
if (Object.keys(this.counters).length < this.rotation) {
this.config.callbacks.updateGrid();
}
}
_updateChart(rid, label, count) {
let labels = this.chart.data.labels;
let data = this.chart.data.datasets[0].data;
let rids = this.chart.data.datasets[0].rids;
let idx = rids.findIndex(x => x === rid);
if (idx === -1) {
labels.push(label);
data.push(count);
rids.push(rid);
} else {
data[idx] = count;
}
this.chart.update();
}
async onMarkupRendered() {
const data = await this.ajaxCall('/api/diagnostics/interface/getInterfaceNames');
this.ifMap = data;
super.openEventSource('/api/diagnostics/firewall/streamLog', this._onMessage.bind(this));
let context = document.getElementById('fw-chart').getContext('2d');
let config = {
type: 'doughnut',
data: {
labels: [],
datasets: [
{
data: [],
rids: [],
}
]
},
options: {
cutout: '40%',
maintainAspectRatio: true,
responsive: true,
aspectRatio: 2,
layout: {
padding: 10
},
normalized: true,
parsing: false,
onClick: (event, elements, chart) => {
const i = elements[0].index;
const rid = chart.data.datasets[0].rids[i];
window.open(`/ui/diagnostics/firewall/log?rid=${rid}`);
},
onHover: (event, elements) => {
event.native.target.style.cursor = elements[0] ? 'pointer' : 'grab';
},
plugins: {
legend: {
display: true,
position: 'left',
onHover: (event, legendItem) => {
const activeElement = {
datasetIndex: 0,
index: legendItem.index
};
this.chart.setActiveElements([activeElement]);
this.chart.tooltip.setActiveElements([activeElement]);
},
labels: {
filter: (ds, data) => {
/* clamp amount of legend labels to a max of 10 (sorted) */
const sortable = [];
data.labels.forEach((l, i) => {
sortable.push([l, data.datasets[0].data[i]]);
});
sortable.sort((a, b) => (b[1] - a[1]));
const sorted = sortable.slice(0, 10).map(e => (e[0]));
return sorted.includes(ds.text)
},
}
},
tooltip: {
callbacks: {
labels: (tooltipItem) => {
let obj = this.counters[tooltipItem.label];
return `${obj.label} (${obj.count})`;
}
}
},
}
},
plugins: [
{
// display a placeholder if no data is available
id: 'nodata_placeholder',
afterDraw: (chart, args, options) => {
if (chart.data.datasets[0].data.length === 0) {
let ctx = chart.ctx;
let width = chart.width;
let height = chart.height;
chart.clear();
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.translations.nodata + '...', width / 2, height / 2);
ctx.restore();
}
}
}
]
}
this.chart = new Chart(context, config);
}
onWidgetClose() {
super.onWidgetClose();
if (this.chart !== null) {
this.chart.destroy();
}
}
onWidgetResize(elem, width, height) {
if (width < 700) {
$('#fw-chart').show();
$('#fw-table-container').hide();
} else {
$('#fw-chart').hide();
$('#fw-table-container').show();
}
}
}