--- /dev/null
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>banIP Map</title>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
+ integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
+ <style>
+ #map {
+ height: 97vh;
+ width: 100%;
+ }
+
+ .mono {
+ font-size: small;
+ font-family: monospace;
+ white-space: nowrap;
+ margin: 0.3em !important;
+ }
+ </style>
+</head>
+
+<body>
+ <div id="map"></div>
+
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
+ integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
+ <script>
+ 'use strict';
+
+ /* get mapData */
+ const mapData = sessionStorage.getItem('mapData');
+ if (mapData) {
+ let uniqueCoordinates = [];
+ let homeCoordinates = [];
+ const parsedData = JSON.parse(mapData);
+ parsedData.forEach(function (item, index) {
+ if (Object.keys(item).length !== 0) {
+ let keys = Object.keys(item);
+ keys.forEach(function (key) {
+ if (item[key] && item[key].lat && item[key].lon && item[key].query && item[key].as && item[key].city && item[key].countryCode) {
+ let coordObj = { lat: item[key].lat, lon: item[key].lon };
+ if (key === "homeIP" && !homeCoordinates.some(existingCoord => existingCoord.lat === coordObj.lat && existingCoord.lon === coordObj.lon)) {
+ coordObj = { key: key, lat: item[key].lat, lon: item[key].lon, query: item[key].query, as: item[key].as, city: item[key].city, cc: item[key].countryCode };
+ homeCoordinates.push(coordObj);
+ }
+ if (key !== "homeIP" && !uniqueCoordinates.some(existingCoord => existingCoord.lat === coordObj.lat && existingCoord.lon === coordObj.lon)) {
+ coordObj = { key: key, lat: item[key].lat, lon: item[key].lon, query: item[key].query, as: item[key].as, city: item[key].city, cc: item[key].countryCode };
+ uniqueCoordinates.push(coordObj);
+ }
+ }
+ });
+ }
+ });
+
+ /* intialize map and map tiles */
+ let map;
+ homeCoordinates.forEach(function (coordObj) {
+ let latHome = coordObj.lat;
+ let lonHome = coordObj.lon;
+ let ipHome = coordObj.query.slice(0, 38);
+ let asHome = coordObj.as.slice(0, 38);
+ let cityHome = coordObj.city.slice(0, 33);
+ let ccHome = coordObj.cc;
+
+ if (typeof map === "undefined") {
+ map = L.map('map', {
+ zoom: 6,
+ minZoom: 2,
+ maxZoom: 18,
+ center: [latHome, lonHome]
+ }).setView([latHome, lonHome]);
+ L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png', {
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+ }).addTo(map);
+ }
+
+ /* render markers for local IPs */
+ let circle = L.circleMarker([latHome, lonHome]).addTo(map);
+ circle.setStyle({ color: '#378242', opacity: 1.0, fillColor: '#378242', fillOpacity: 0.5, radius: 6 });
+ circle.bindPopup("<b><center>local IP</center></b>" +
+ "<p class=\"mono\">" +
+ "City: " + cityHome + " (" + ccHome + ")" +
+ "<br />Latitude : " + latHome +
+ "<br />Longitude: " + lonHome +
+ "<br />IP: " + ipHome +
+ "<br />AS: " + asHome +
+ "</p>");
+ });
+
+ /* render markers for blocked IPs */
+ uniqueCoordinates.forEach(function (coordObj) {
+ let key = coordObj.key.slice(0, -3);
+ let lat = coordObj.lat;
+ let lon = coordObj.lon;
+ let ip = coordObj.query.slice(0, 38);
+ let as = coordObj.as.slice(0, 38);
+ let city = coordObj.city.slice(0, 33);
+ let cc = coordObj.cc;
+ let circle = L.circleMarker([lat, lon]).addTo(map);
+ circle.setStyle({ color: '#C22121', opacity: 1.0, fillColor: '#C22121', fillOpacity: 0.5, radius: 2 });
+ circle.bindPopup("<b><center>blocked IP by " + key + "</center></b>" +
+ "<p class=\"mono\">" +
+ "City: " + city + " (" + cc + ")" +
+ "<br />Latitude : " + lat +
+ "<br />Longitude: " + lon +
+ "<br />IP: " + ip +
+ "<br />AS: " + as +
+ "</p>");
+ });
+ }
+ </script>
+</body>
+
+</html>
\ No newline at end of file
'require view';
'require fs';
'require ui';
+'require uci';
/*
button handling
document.getElementById('result').textContent = 'The search is running, please wait...';
return L.resolveDefault(fs.exec_direct('/etc/init.d/banip', ['search', ip])).then(function (res) {
let result = document.getElementById('result');
- if (res) {
- result.textContent = res.trim();
- } else {
- result.textContent = _('No Search results!');
- }
+ result.textContent = res.trim();
document.getElementById('search').value = '';
})
}
document.getElementById('search').focus();
})
- }, _('Search'))
+ }, _('Search IP'))
])
]);
document.getElementById('search').focus();
}
- if (ev === 'survey') {
- let content, selectOption;
+ if (ev === 'content') {
+ let content, selectOption, errMsg;
if (report[1]) {
try {
content = JSON.parse(report[1]);
} catch (e) {
content = "";
- ui.addNotification(null, E('p', _('Unable to parse the ruleset file!')), 'error');
+ if (!errMsg) {
+ errMsg = true;
+ return ui.addNotification(null, E('p', _('Unable to parse the ruleset file!')), 'error');
+ }
}
} else {
- content = "";
+ return;
}
selectOption = [E('option', { value: '' }, [_('-- Set Selection --')])];
- for (let i = 0; i < Object.keys(content.nftables).length; i++) {
- if (content.nftables[i].set && content.nftables[i].set.name !== undefined && content.nftables[i].set.table !== undefined && content.nftables[i].set.table === 'banIP') {
- selectOption.push(E('option', { 'value': content.nftables[i].set.name }, content.nftables[i].set.name));
- }
- }
- L.ui.showModal(_('Set Survey'), [
+ Object.keys(content.nftables)
+ .filter(key => content.nftables[key].set?.name && content.nftables[key].set.table === 'banIP')
+ .sort((a, b) => content.nftables[a].set.name.localeCompare(content.nftables[b].set.name))
+ .forEach(key => {
+ selectOption.push(E('option', { 'value': content.nftables[key].set.name }, content.nftables[key].set.name));
+ })
+ L.ui.showModal(_('Set Content'), [
E('p', _('List the elements of a specific banIP-related Set.')),
E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em', 'id': 'run' }, [
'click': ui.createHandlerFn(this, function (ev) {
let set = document.getElementById('set').value;
if (set) {
- document.getElementById('result').textContent = 'The survey is running, please wait...';
- return L.resolveDefault(fs.exec_direct('/etc/init.d/banip', ['survey', set])).then(function (res) {
+ document.getElementById('result').textContent = 'Collecting Set content, please wait...';
+ return L.resolveDefault(fs.exec_direct('/etc/init.d/banip', ['content', set])).then(function (res) {
let result = document.getElementById('result');
- if (res) {
- result.textContent = res.trim();
- } else {
- result.textContent = _('No Search results!');
- }
+ result.textContent = res.trim();
document.getElementById('set').value = '';
})
}
document.getElementById('set').focus();
})
- }, _('Survey'))
+ }, _('Show Content'))
])
]);
document.getElementById('set').focus();
}
+ if (ev === 'map') {
+ let md = L.ui.showModal(null, [
+ E('div', { id: 'mapModal',
+ style: 'position: relative;' }, [
+ E('iframe', {
+ id: 'mapFrame',
+ src: L.resource('view/banip/map.html'),
+ style: 'width: 100%; height: 80vh; border: none;'
+ }),
+ ]),
+ E('div', { 'class': 'right' }, [
+ E('button', {
+ 'class': 'btn cbi-button',
+ 'click': function() {
+ L.hideModal();
+ sessionStorage.clear();
+ }
+ }, _('Cancel')),
+ ' ',
+ E('button', {
+ 'class': 'btn cbi-button-action',
+ 'click': ui.createHandlerFn(this, function (ev) {
+ let iframe = document.getElementById('mapFrame');
+ iframe.contentWindow.location.reload();
+ })
+ }, _('Map Reset'))
+ ])
+ ]);
+ md.style.maxWidth = '90%';
+ document.getElementById('mapModal').focus();
+ }
}
return view.extend({
load: function () {
return Promise.all([
L.resolveDefault(fs.exec_direct('/etc/init.d/banip', ['report', 'json']), ''),
- L.resolveDefault(fs.exec_direct('/usr/sbin/nft', ['-tj', 'list', 'table', 'inet', 'banIP']), '')
+ L.resolveDefault(fs.exec_direct('/usr/sbin/nft', ['-tj', 'list', 'sets']), ''),
+ uci.load('banip')
]);
},
render: function (report) {
- let content, rowSets, tblSets;
+ let content, rowSets, tblSets, notMsg, errMsg;
if (report[0]) {
try {
content = JSON.parse(report[0]);
} catch (e) {
content = "";
- ui.addNotification(null, E('p', _('Unable to parse the report file!')), 'error');
+ if (!errMsg) {
+ errMsg = true;
+ ui.addNotification(null, E('p', _('Unable to parse the report file!')), 'error');
+ }
}
} else {
content = "";
])
]);
- if (content.sets) {
+ if (content[0] && content[0].sets) {
let cnt1, cnt2;
- Object.keys(content.sets).forEach(function (key) {
- cnt1 = content.sets[key].cnt_inbound ? ': (' + content.sets[key].cnt_inbound + ')' : '';
- cnt2 = content.sets[key].cnt_outbound ? ': (' + content.sets[key].cnt_outbound + ')' : '';
+
+ Object.keys(content[0].sets).sort().forEach(function (key) {
+ cnt1 = content[0].sets[key].cnt_inbound ? ': (' + content[0].sets[key].cnt_inbound + ')' : '';
+ cnt2 = content[0].sets[key].cnt_outbound ? ': (' + content[0].sets[key].cnt_outbound + ')' : '';
rowSets.push([
E('em', key),
- E('em', { 'style': 'padding-right: 20px' }, content.sets[key].cnt_elements),
- E('em', content.sets[key].inbound + cnt1),
- E('em', content.sets[key].outbound + cnt2),
- E('em', content.sets[key].port),
- E('em', content.sets[key].set_elements)
+ E('em', { 'style': 'padding-right: 20px' }, content[0].sets[key].cnt_elements),
+ E('em', content[0].sets[key].inbound + cnt1),
+ E('em', content[0].sets[key].outbound + cnt2),
+ E('em', content[0].sets[key].port),
+ E('em', content[0].sets[key].set_elements.join(", "))
]);
});
rowSets.push([
- E('em', { 'style': 'font-weight: bold' }, content.sum_sets),
- E('em', { 'style': 'font-weight: bold; padding-right: 20px' }, content.sum_cntelements),
- E('em', { 'style': 'font-weight: bold' }, content.sum_setinbound + ' (' + content.sum_cntinbound + ')'),
- E('em', { 'style': 'font-weight: bold' }, content.sum_setoutbound + ' (' + content.sum_cntoutbound + ')'),
- E('em', { 'style': 'font-weight: bold' }, content.sum_setports),
- E('em', { 'style': 'font-weight: bold' }, content.sum_setelements)
+ E('em', { 'style': 'font-weight: bold' }, content[0].sum_sets),
+ E('em', { 'style': 'font-weight: bold; padding-right: 20px' }, content[0].sum_cntelements),
+ E('em', { 'style': 'font-weight: bold' }, content[0].sum_setinbound + ' (' + content[0].sum_cntinbound + ')'),
+ E('em', { 'style': 'font-weight: bold' }, content[0].sum_setoutbound + ' (' + content[0].sum_cntoutbound + ')'),
+ E('em', { 'style': 'font-weight: bold' }, content[0].sum_setports),
+ E('em', { 'style': 'font-weight: bold' }, content[0].sum_setelements)
]);
}
cbi_update_table(tblSets, rowSets);
- return E('div', { 'class': 'cbi-map', 'id': 'map' }, [
+ return E('div', { 'class': 'cbi-map', 'id': 'cbimap' }, [
E('div', { 'class': 'cbi-section' }, [
- E('p', _('This tab shows the last generated Set Report, press the \'Refresh\' button to get a new one.')),
+ E('p', _('This report shows the latest NFT Set statistics, press the \'Refresh\' button to get a new one. \
+ You can also display the specific content of Sets, search for suspicious IPs and finally, these IPs can also be displayed on a map.')),
E('p', '\xa0'),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;width:230px;font-weight:bold;' }, _('Timestamp')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-bottom:-5px;color:#37c;font-weight:bold;' }, content.timestamp || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-bottom:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.timestamp || '-')
]),
E('hr'),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked syn-flood packets')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content.sum_synflood || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_synflood || '-')
]),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked udp-flood packets')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content.sum_udpflood || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_udpflood || '-')
]),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked icmp-flood packets')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content.sum_icmpflood || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_icmpflood || '-')
]),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked invalid ct packets')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content.sum_ctinvalid || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_ctinvalid || '-')
]),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked invalid tcp packets')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content.sum_tcpinvalid || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_tcpinvalid || '-')
]),
E('hr'),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('auto-added IPs to allowlist')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content.autoadd_allow || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.autoadd_allow || '-')
]),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('auto-added IPs to blocklist')),
- E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content.autoadd_block || '-')
+ E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.autoadd_block || '-')
])
]),
E('br'),
'class': 'btn cbi-button cbi-button-apply',
'style': 'float:none;margin-right:.4em;',
'click': ui.createHandlerFn(this, function () {
- return handleAction(report, 'survey');
+ if (uci.get('banip', 'global', 'ban_nftcount') !== '1' || uci.get('banip', 'global', 'ban_map') !== '1') {
+ if (!notMsg) {
+ notMsg = true;
+ return ui.addNotification(null, E('p', _('GeoIP Map is not enabled!')), 'info');
+ }
+ }
+ if (content[1] && content[1].length > 1) {
+ sessionStorage.setItem('mapData', JSON.stringify(content[1]));
+ return handleAction(report, 'map');
+ }
+ else {
+ if (!notMsg) {
+ notMsg = true;
+ return ui.addNotification(null, E('p', _('No GeoIP Map data!')), 'info');
+ }
+ }
+ })
+ }, [_('Map...')]),
+ E('button', {
+ 'class': 'btn cbi-button cbi-button-apply',
+ 'style': 'float:none;margin-right:.4em;',
+ 'click': ui.createHandlerFn(this, function () {
+ return handleAction(report, 'content');
})
- }, [_('Set Survey...')]),
+ }, [_('Set Content...')]),
E('button', {
'class': 'btn cbi-button cbi-button-apply',
'style': 'float:none;margin-right:.4em;',