diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js index 7640d39dc0..d883aa513f 100644 --- a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js +++ b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js @@ -180,7 +180,7 @@ return view.extend({ ]), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Last Run')), - E('div', { 'class': 'cbi-value-field', 'id': 'last', 'style': 'margin - bottom:- 5px; color:#37c; ' }, ' - ') + E('div', { 'class': 'cbi-value-field', 'id': 'last', 'style': 'margin-bottom:- 5px; color:#37c; ' }, ' - ') ]), E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('System Info')), diff --git a/applications/luci-app-banip/Makefile b/applications/luci-app-banip/Makefile index 3a494c5091..5231e2ea1e 100644 --- a/applications/luci-app-banip/Makefile +++ b/applications/luci-app-banip/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=LuCI support for banIP LUCI_DEPENDS:=+luci-base +banip -PKG_VERSION:=1.8.0 -PKG_RELEASE:=3 +PKG_VERSION:=1.8.1 +PKG_RELEASE:=1 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=Dirk Brenken diff --git a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/allowlist.js b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/allowlist.js index 678e3c2389..30ba7038db 100644 --- a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/allowlist.js +++ b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/allowlist.js @@ -4,7 +4,8 @@ 'require ui'; const localFile = '/etc/banip/banip.allowlist'; -let notMsg = false, errMsg = false; +const maxSize = 100000; +let notMsg = false; const resetScroll = () => { document.body.scrollTop = document.documentElement.scrollTop = 0; @@ -12,19 +13,24 @@ const resetScroll = () => { return view.extend({ load: function () { - return L.resolveDefault(fs.stat(localFile), "") + return L.resolveDefault(fs.stat(localFile), null) .then(function (stat) { - if (!stat) { - return fs.write(localFile, ""); - } - return Promise.all([ - L.resolveDefault(fs.stat(localFile), ""), - L.resolveDefault(fs.read_direct(localFile), "") - ]); - }); + if (!stat) { + return fs.write(localFile, "").then(() => [{ size: 0 }, ""]); + } + return Promise.all([ + Promise.resolve(stat), + L.resolveDefault(fs.read_direct(localFile), "") + ]); + }); }, + render: function (allowlist) { - if (allowlist[0] && allowlist[0].size >= 100000) { + const size = allowlist[0] ? allowlist[0].size : 0; + const content = allowlist[1] != null ? allowlist[1] : ''; + const tooBig = size >= maxSize; + + if (tooBig) { resetScroll(); ui.addNotification(null, E('p', _('The allowlist is too big, unable to save modifications.')), 'error'); } @@ -35,28 +41,29 @@ return view.extend({ 'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em', 'spellcheck': 'false', 'wrap': 'off', - 'rows': 25 - }, [allowlist[1] != null ? allowlist[1] : '']) + 'rows': 25, + 'readonly': tooBig ? 'readonly' : null, + 'input': function () { notMsg = false; } + }, [content]) ]); }, - handleSave: function (ev) { - let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n?/g, '\n')); + + handleSave: function (_ev) { + const value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n?/g, '\n')); return fs.write(localFile, value + "\n") - .then(function () { - document.querySelector('textarea').value = value; - resetScroll(); - if (!notMsg) { - ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload banIP that changes take effect.')), 'info'); - notMsg = true; - } - }).catch(function (e) { - resetScroll(); - if (!errMsg) { + .then(function () { + document.querySelector('textarea').value = value + "\n"; + resetScroll(); + if (!notMsg) { + notMsg = true; + ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload banIP that changes take effect.')), 'info'); + } + }).catch(function (e) { + resetScroll(); ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error'); - errMsg = true; - } - }); + }); }, + handleSaveApply: null, handleReset: null -}); +}); \ No newline at end of file diff --git a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/blocklist.js b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/blocklist.js index ad5c3fd39e..9832f2c952 100644 --- a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/blocklist.js +++ b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/blocklist.js @@ -4,7 +4,8 @@ 'require ui'; const localFile = '/etc/banip/banip.blocklist'; -let notMsg = false, errMsg = false; +const maxSize = 100000; +let notMsg = false; const resetScroll = () => { document.body.scrollTop = document.documentElement.scrollTop = 0; @@ -12,19 +13,24 @@ const resetScroll = () => { return view.extend({ load: function () { - return L.resolveDefault(fs.stat(localFile), "") + return L.resolveDefault(fs.stat(localFile), null) .then(function (stat) { - if (!stat) { - return fs.write(localFile, ""); - } - return Promise.all([ - L.resolveDefault(fs.stat(localFile), ""), - L.resolveDefault(fs.read_direct(localFile), "") - ]); - }); + if (!stat) { + return fs.write(localFile, "").then(() => [{ size: 0 }, ""]); + } + return Promise.all([ + Promise.resolve(stat), + L.resolveDefault(fs.read_direct(localFile), "") + ]); + }); }, + render: function (blocklist) { - if (blocklist[0] && blocklist[0].size >= 100000) { + const size = blocklist[0] ? blocklist[0].size : 0; + const content = blocklist[1] != null ? blocklist[1] : ''; + const tooBig = size >= maxSize; + + if (tooBig) { resetScroll(); ui.addNotification(null, E('p', _('The blocklist is too big, unable to save modifications.')), 'error'); } @@ -35,28 +41,29 @@ return view.extend({ 'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em', 'spellcheck': 'false', 'wrap': 'off', - 'rows': 25 - }, [blocklist[1] != null ? blocklist[1] : '']) + 'rows': 25, + 'readonly': tooBig ? 'readonly' : null, + 'input': function () { notMsg = false; } + }, [content]) ]); }, - handleSave: function (ev) { - let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n?/g, '\n')); + + handleSave: function (_ev) { + const value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n?/g, '\n')); return fs.write(localFile, value + "\n") - .then(function () { - document.querySelector('textarea').value = value; - resetScroll(); - if (!notMsg) { - ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload banIP that changes take effect.')), 'info'); - notMsg = true; - } - }).catch(function (e) { - resetScroll(); - if (!errMsg) { + .then(function () { + document.querySelector('textarea').value = value + "\n"; + resetScroll(); + if (!notMsg) { + notMsg = true; + ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload banIP that changes take effect.')), 'info'); + } + }).catch(function (e) { + resetScroll(); ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error'); - errMsg = true; - } - }); + }); }, + handleSaveApply: null, handleReset: null -}); +}); \ No newline at end of file diff --git a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/feeds.js b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/feeds.js index 25ad62693c..11a629d64d 100644 --- a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/feeds.js +++ b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/feeds.js @@ -13,6 +13,26 @@ document.querySelector('head').appendChild(E('link', { 'href': L.resource('view/banip/custom.css') })); +/* + module-level file size set during render, read by observer +*/ +let fileSize = 0; + +/* + button state helper +*/ +function updateButtons() { + const buttons = document.querySelectorAll('#btnClear, #btnCreate, #btnSave, #btnUpload, #btnDownload'); + if (fileSize === 0) { + if (buttons[1]) buttons[1].removeAttribute('disabled'); + if (buttons[2]) buttons[2].removeAttribute('disabled'); + } else { + if (buttons[0]) buttons[0].removeAttribute('disabled'); + if (buttons[3]) buttons[3].removeAttribute('disabled'); + if (buttons[4]) buttons[4].removeAttribute('disabled'); + } +} + /* observe DOM changes */ @@ -29,17 +49,7 @@ const observer = new MutationObserver(function (mutations) { labels.forEach(function (label) { label.setAttribute("style", "font-weight: bold !important; color: #595 !important;"); }) - L.resolveDefault(fs.stat('/etc/banip/banip.custom.feeds'), '').then(function (stat) { - const buttons = document.querySelectorAll('#btnClear, #btnCreate, #btnSave, #btnUpload, #btnDownload'); - if (buttons[1] && buttons[2] && stat.size === 0) { - buttons[1].removeAttribute('disabled'); - buttons[2].removeAttribute('disabled'); - } else if (buttons[0] && buttons[3] && buttons[4] && stat.size > 0) { - buttons[0].removeAttribute('disabled'); - buttons[3].removeAttribute('disabled'); - buttons[4].removeAttribute('disabled'); - } - }); + updateButtons(); } }); @@ -59,29 +69,20 @@ function handleEdit(ev) { if (ev === 'upload') { return ui.uploadFile('/etc/banip/banip.custom.feeds').then(function () { L.resolveDefault(fs.read_direct('/etc/banip/banip.custom.feeds', 'json'), "").then(function (data) { - if (data) { - let dataLength = Object.keys(data).length || 0; - if (dataLength > 0) { - for (let i = 0; i < dataLength; i++) { - let feed = Object.keys(data)[i]; - let descr = data[feed].descr; - if (feed && descr) { - continue; - } - fs.write('/etc/banip/banip.custom.feeds', null).then(function () { - ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error'); - }); - return; + if (data && Object.keys(data).length > 0) { + for (let i = 0; i < Object.keys(data).length; i++) { + let feed = Object.keys(data)[i]; + let descr = data[feed].descr; + if (feed && descr) { + continue; } - } else { - fs.write('/etc/banip/banip.custom.feeds', null).then(function () { + return fs.write('/etc/banip/banip.custom.feeds', null).then(function () { ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error'); }); - return; } location.reload(); } else { - fs.write('/etc/banip/banip.custom.feeds', null).then(function () { + return fs.write('/etc/banip/banip.custom.feeds', null).then(function () { ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error'); }); } @@ -120,9 +121,9 @@ function handleEdit(ev) { } } /* - gather all input data + gather all input data and fall through from 'save' */ - let sumSubElements = []; + const exportObj = {}; const nodeKeys = document.querySelectorAll('[id^="widget.cbid.json"][id$="name"]'); for (const keyNode of nodeKeys) { const keyValue = keyNode.value?.trim(); @@ -144,40 +145,39 @@ function handleEdit(ev) { sub[key] = value; } } - if (sub.descr) { - sumSubElements.push(keyValue, sub); + /* require at least descr and rule to produce a valid feed entry */ + if (sub.descr && sub.rule && (sub.url_4 || sub.url_6)) { + exportObj[keyValue] = sub; } } - /* - construct json object - */ - let exportObj = {}; - for (let i = 0; i < sumSubElements.length; i += 2) { - const key = sumSubElements[i]; - const value = sumSubElements[i + 1]; - exportObj[key] = value; - } - const exportJson = JSON.stringify(exportObj, null, 4); /* save to file and reload */ + const exportJson = JSON.stringify(exportObj, null, 4); return fs.write('/etc/banip/banip.custom.feeds', exportJson) .then(() => location.reload()); } return view.extend({ load: function () { - return L.resolveDefault(fs.stat('/etc/banip/banip.custom.feeds'), "") + return L.resolveDefault(fs.stat('/etc/banip/banip.custom.feeds'), null) .then(function (stat) { if (!stat) { - return fs.write('/etc/banip/banip.custom.feeds', ""); + return fs.write('/etc/banip/banip.custom.feeds', "").then(function () { + return { size: 0, data: null }; + }); } - return L.resolveDefault(fs.read_direct('/etc/banip/banip.custom.feeds', 'json'), ""); - }) + return L.resolveDefault(fs.read_direct('/etc/banip/banip.custom.feeds', 'json'), "") + .then(function (data) { + return { size: stat.size, data: data }; + }); + }); }, - render: function (data) { + render: function (result) { let m, s, o, feed, url_4, url_6, rule, chain, descr, flag; + const data = result.data; + fileSize = result.size; m = new form.JSONMap(data, null, _('With this editor you can upload your local custom feed file or fill up an initial one (a 1:1 copy of the version shipped with the package). \ The file is located at \'/etc/banip/banip.custom.feeds\'. \ @@ -213,7 +213,7 @@ return view.extend({ if (!value) { return true; } - if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-\?\&\+_@%=:~#]+$/)) { + if (!value.match(/^https?:\/\/[A-Za-z0-9\[\]\/.\-?&+_@%=:~#]+$/)) { return _('Protocol/URL format not supported'); } return true; @@ -224,7 +224,7 @@ return view.extend({ if (!value) { return true; } - if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-\?\&\+_@%=:~#]+$/)) { + if (!value.match(/^https?:\/\/[A-Za-z0-9\[\]\/.\-?&+_@%=:~#]+$/)) { return _('Protocol/URL format not supported'); } return true; @@ -320,4 +320,4 @@ return view.extend({ handleSaveApply: null, handleSave: null, handleReset: null -}); +}); \ No newline at end of file diff --git a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/logtemplate.js b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/logtemplate.js index c26aa23bbd..9ac4d6752c 100644 --- a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/logtemplate.js +++ b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/logtemplate.js @@ -12,37 +12,39 @@ function Logview(logtag, name) { return L.view.extend({ load: () => Promise.resolve(), - render: () => { - L.Poll.add(() => { + render: function () { + const pollFn = () => { return callLogRead(1000, false, true).then(res => { const logEl = document.getElementById('logfile'); if (!logEl) return; const filtered = (res?.log ?? []) - .filter(entry => !logtag || entry.msg.includes(logtag)) - .map(entry => { - const d = new Date(entry.time); - const date = d.toLocaleDateString([], { - year: 'numeric', - month: '2-digit', - day: '2-digit' + .filter(entry => !logtag || entry.msg.includes(logtag)) + .map(entry => { + const d = new Date(entry.time * 1000); + const date = d.toLocaleDateString([], { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }); + const time = d.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + return `[${date}-${time}] ${entry.msg}`; }); - const time = d.toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - }); - return `[${date}-${time}] ${entry.msg}`; - }); - if (filtered.length > 0) { - logEl.value = filtered.join('\n'); - } else { - logEl.value = _('No %s related logs yet!').format(name); - } + + logEl.value = filtered.length > 0 + ? filtered.join('\n') + : _('No %s related logs yet!').format(name); logEl.scrollTop = logEl.scrollHeight; }); - }); + }; + + this._pollFn = pollFn; + L.Poll.add(pollFn); return E('div', { class: 'cbi-map' }, [ E('div', { class: 'cbi-section' }, [ @@ -58,10 +60,17 @@ function Logview(logtag, name) { ]); }, + unload: function () { + if (this._pollFn) { + L.Poll.remove(this._pollFn); + this._pollFn = null; + } + }, + handleSaveApply: null, handleSave: null, handleReset: null }); } -return L.Class.extend({ Logview }); +return L.Class.extend({ Logview }); \ No newline at end of file diff --git a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/overview.js b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/overview.js index b3d119ec8b..1ee1bed1d3 100644 --- a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/overview.js +++ b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/overview.js @@ -60,56 +60,69 @@ return view.extend({ /* poll runtime information */ + let parseErrCount = 0; poll.add(function () { - return L.resolveDefault(fs.read_direct('/var/run/banip_runtime.json'), 'null').then(function (res) { - const status = document.getElementById('status'); - const buttons = document.querySelectorAll('.cbi-page-actions button'); - try { - var info = JSON.parse(res); - } catch (e) { - status.textContent = '-'; - if (status.classList.contains('spinning')) { - buttons.forEach(function (btn) { - btn.disabled = false; - }) - status.classList.remove('spinning'); - } - ui.addNotification(null, E('p', _('Unable to parse the runtime information!')), 'error'); + return L.resolveDefault(fs.stat('/var/run/banip_runtime.json'), null).then(function (stat) { + if (!stat) { return; } - if (status && info) { - status.textContent = `${info.status || '-'} (frontend: ${info.frontend_ver || '-'} / backend: ${info.backend_ver || '-'})`; - if (info.status === "processing") { - if (!status.classList.contains("spinning")) { - status.classList.add("spinning"); - } - buttons.forEach(function (btn) { - btn.disabled = true; - btn.blur(); - }) - } else { - if (status.classList.contains("spinning")) { + return L.resolveDefault(fs.read_direct('/var/run/banip_runtime.json'), 'null').then(function (res) { + const status = document.getElementById('status'); + const buttons = document.querySelectorAll('.cbi-page-actions button'); + let info = null; + try { + info = JSON.parse(res); + parseErrCount = 0; + } catch (e) { + info = null; + parseErrCount++; + if (status) { + status.textContent = '-'; buttons.forEach(function (btn) { btn.disabled = false; - }) - status.classList.remove("spinning"); + }); + status.classList.remove('spinning'); + if (parseErrCount >= 3) { + ui.addNotification(null, E('p', _('Unable to parse the banIP runtime information!')), 'error'); + poll.stop(); + } + } + return; + } + if (status && info) { + status.textContent = `${info.status || '-'} (frontend: ${info.frontend_ver || '-'} / backend: ${info.backend_ver || '-'})`; + if (info.status === "processing") { + buttons.forEach(function (btn) { + btn.disabled = true; + btn.blur(); + }); + if (!status.classList.contains("spinning")) { + status.classList.add("spinning"); + } + } else { + if (status.classList.contains("spinning")) { + status.classList.remove("spinning"); + buttons.forEach(function (btn) { + btn.disabled = false; + }); + } } } - } - if (info) { - setText('elements', info.element_count); - setText('feeds', info.active_feeds?.join(', ')); - setText('devices', `wan-dev: ${info.wan_devices?.join(', ') || '-'} / + if (info) { + setText('elements', info.element_count); + setText('feeds', info.active_feeds?.join(', ') || '-'); + setText('devices', `wan-dev: ${info.wan_devices?.join(', ') || '-'} / wan-if: ${info.wan_interfaces?.join(', ') || '-'} / vlan-allow: ${info.vlan_allow?.join(', ') || '-'} / vlan-block: ${info.vlan_block?.join(', ') || '-'}`); - setText('uplink', info.active_uplink?.join(', ') || '-'); - setText('nft', info.nft_info); - setText('run', info.run_info); - setText('flags', info.run_flags); - setText('last', info.last_run); - setText('sys', info.system_info); - } + setText('uplink', info.active_uplink?.join(', ') || '-'); + setText('nft', info.nft_info); + setText('run', info.run_info); + setText('flags', info.run_flags); + setText('last', info.last_run); + setText('sys', info.system_info); + } + }); }); }, 2); @@ -232,7 +245,7 @@ return view.extend({ o.optional = true; o.retain = true; - o = s.taboption('general', form.Value, 'ban_fetchparm', _('Download Parameters'), _('Override the pre-configured download options for the selected download utility.')) + o = s.taboption('general', form.Value, 'ban_fetchparm', _('Download Parameters'), _('Override the pre-configured download options for the selected download utility.')); o.depends('ban_autodetect', '0'); o.optional = true; o.retain = true; @@ -331,7 +344,7 @@ return view.extend({ o.rmempty = true; o = s.taboption('advanced', form.Flag, 'ban_deduplicate', _('Deduplicate IPs'), _('Deduplicate IP addresses across all active Sets and tidy up the local blocklist.')); - o.default = 1 + o.default = 1; o.rmempty = false; /* @@ -507,7 +520,7 @@ return view.extend({ o.optional = true; o.rmempty = true; - o = s.taboption('adv_set', form.MultiValue, 'ban_feedfreset', _('Feed Flag Reset'), _('Override the default feed configuration and remove existing port/protocol limitations.')); + o = s.taboption('adv_set', form.MultiValue, 'ban_feedreset', _('Feed Flag Reset'), _('Override the default feed configuration and remove existing port/protocol limitations.')); o.value('allowlist', _('local allowlist')); o.value('blocklist', _('local blocklist')); feedKeys.forEach(f => o.value(f.trim())); @@ -592,7 +605,7 @@ return view.extend({ o.rmempty = true; o = s.taboption('adv_log', form.Flag, 'ban_remotelog', _('Enable Remote Logging'), _('Enable the cgi interface to receive remote logging events.')); - o.default = 0 + o.default = 0; o.optional = true; o.rmempty = true; @@ -607,7 +620,7 @@ return view.extend({ return _('Invalid characters'); } return true; - } + }; o.optional = true; o.rmempty = true; @@ -616,7 +629,7 @@ return view.extend({ */ o = s.taboption('adv_email', form.DummyValue, '_sub'); o.rawhtml = true; - o.default = '' + _('To enable email notifications, set up the \'msmtp\' package and specify a vaild E-Mail receiver address.') + '' + o.default = '' + _('To enable email notifications, set up the \'msmtp\' package and specify a valid E-Mail receiver address.') + '' + '
'; o = s.taboption('adv_email', form.Flag, 'ban_mailnotification', _('E-Mail Notification'), _('Receive E-Mail notifications with every banIP run.')); @@ -661,7 +674,7 @@ return view.extend({ o.rmempty = true; } - o = s.taboption('feeds', form.DummyValue, '_feeds'); + o = s.taboption('feeds', form.DummyValue, '_feeds1'); o.rawhtml = true; o.default = '
' + _('Country Selection') + ''; @@ -703,7 +716,7 @@ return view.extend({ o = s.taboption('feeds', form.Flag, 'ban_countrysplit', _('Split Country Set'), _('The selected Countries are stored in separate Sets.')); o.rmempty = true; - o = s.taboption('feeds', form.DummyValue, '_feeds'); + o = s.taboption('feeds', form.DummyValue, '_feeds2'); o.rawhtml = true; o.default = '
' + _('ASN Selection') + ''; @@ -715,7 +728,7 @@ return view.extend({ o = s.taboption('feeds', form.Flag, 'ban_asnsplit', _('Split ASN Set'), _('The selected ASNs are stored in separate Sets.')); o.rmempty = true; - o = s.taboption('feeds', form.DummyValue, '_feeds'); + o = s.taboption('feeds', form.DummyValue, '_feeds3'); o.rawhtml = true; o.default = '
' + _('External Allowlist Feeds') + ''; @@ -743,15 +756,15 @@ return view.extend({ return _('Invalid URL format'); } return true; - } + }; } - o = s.taboption('feeds', form.DummyValue, '_feeds'); + o = s.taboption('feeds', form.DummyValue, '_feeds4'); o.rawhtml = true; o.default = '
' + _('Local Feed Settings') + ''; o = s.taboption('feeds', form.Flag, 'ban_autoallowlist', _('Auto Allowlist'), _('Automatically add resolved domains and uplink IPs to the local banIP allowlist.')); - o.default = 1 + o.default = 1; o.rmempty = false; o = s.taboption('feeds', form.ListValue, 'ban_autoallowuplink', _('Auto Allow Uplink'), _('Limit the uplink autoallow function.')); @@ -766,11 +779,11 @@ return view.extend({ o.rmempty = true; o = s.taboption('feeds', form.Flag, 'ban_autoblocklist', _('Auto Blocklist'), _('Automatically add resolved domains and suspicious IPs to the local banIP blocklist.')); - o.default = 1 + o.default = 1; o.rmempty = false; o = s.taboption('feeds', form.Flag, 'ban_autoblocksubnet', _('Auto Block Subnet'), _('Automatically add entire subnets to the blocklist Set based on an additional RDAP request with the suspicious IP.')); - o.default = 0 + o.default = 0; o.optional = true; o.rmempty = true; @@ -791,8 +804,8 @@ return view.extend({ if (!value) { return true; } - if (!value.match(/^[1-9][0-9]*(ms|s|m|h|d|w)$/)) { - return _('Invalid expiry format'); + if (!value.match(/^([1-9][0-9]*(ms|s|m|h|d|w))+$/)) { + return _('Invalid expiry format, e.g. 5m, 2h, 1d or 1h30m'); } return true; }; @@ -824,7 +837,7 @@ return view.extend({ return handleAction('restart'); }) }, [_('Save & Restart')]) - ]) + ]); }); return m.render(); }, diff --git a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/setreport.js b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/setreport.js index 8768cbab8e..1f59bd1215 100644 --- a/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/setreport.js +++ b/applications/luci-app-banip/htdocs/luci-static/resources/view/banip/setreport.js @@ -7,6 +7,8 @@ /* button handling */ +let errMsg = false; + function handleAction(report, ev) { if (ev === 'search') { ui.showModal(_('IP Search'), [ @@ -60,7 +62,7 @@ function handleAction(report, ev) { document.getElementById('search').focus(); } if (ev === 'content') { - let content, selectOption, errMsg; + let content, selectOption; if (report[1]) { try { @@ -77,11 +79,11 @@ function handleAction(report, ev) { } selectOption = [E('option', { value: '' }, [_('-- Set Selection --')])]; 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)); - }) + .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)); + }) 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' }, [ @@ -126,7 +128,7 @@ function handleAction(report, ev) { 'class': 'btn cbi-button-action', 'click': ui.createHandlerFn(this, function (ev) { const checkbox = document.getElementById('chkFilter'); - const isChecked = checkbox.checked; + const isChecked = checkbox.checked ? 'true' : 'false'; let set = document.getElementById('set').value; if (set) { document.getElementById('result').textContent = 'Collecting Set content, please wait...'; @@ -151,8 +153,10 @@ function handleAction(report, ev) { } if (ev === 'map') { const modal = ui.showModal(null, [ - E('div', { id: 'mapModal', - style: 'position: relative;' }, [ + E('div', { + id: 'mapModal', + style: 'position: relative;' + }, [ E('iframe', { id: 'mapFrame', src: L.resource('view/banip/map.html'), @@ -193,7 +197,7 @@ return view.extend({ }, render: function (report) { - let content=[], rowSets, tblSets, notMsg, errMsg; + let content = [], rowSets, tblSets, notMsg; if (report) { try { @@ -228,7 +232,7 @@ return view.extend({ 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(", ")) + E('em', content[0].sets[key].set_elements.join(", ")) ]); }); rowSets.push([ @@ -249,41 +253,41 @@ return view.extend({ 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('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?.[0]?.timestamp || '-') + E('div', { 'class': 'cbi-value-title', '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?.[0]?.sum_synflood || '-') + E('div', { 'class': 'cbi-value-title', '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?.[0]?.sum_udpflood || '-') + E('div', { 'class': 'cbi-value-title', '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?.[0]?.sum_icmpflood || '-') + E('div', { 'class': 'cbi-value-title', '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?.[0]?.sum_ctinvalid || '-') + E('div', { 'class': 'cbi-value-title', '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?.[0]?.sum_tcpinvalid || '-') + E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_tcpinvalid || '-') ]), E('div', { 'class': 'cbi-value' }, [ E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;width:230px;font-weight:bold;' }, _('blocked bcp38 packets')), - E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_bcp38 || '-') + E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.sum_bcp38 || '-') ]), 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?.[0]?.autoadd_allow || '-') + E('div', { 'class': 'cbi-value-title', '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?.[0]?.autoadd_block || '-') + E('div', { 'class': 'cbi-value-title', 'style': 'margin-top:-5px;color:#37c;font-weight:bold;' }, content?.[0]?.autoadd_block || '-') ]) ]), E('br'), @@ -300,11 +304,10 @@ return view.extend({ 'id': 'btnMap', 'disabled': 'disabled', 'click': ui.createHandlerFn(this, function () { - if (content[1] && content[1].length > 1) { + if (Array.isArray(content[1]) && content[1].length > 1) { sessionStorage.setItem('mapData', JSON.stringify(content[1])); return handleAction(report, 'map'); - } - else { + } else { if (!notMsg) { notMsg = true; return ui.addNotification(null, E('p', _('No GeoIP Map data!')), 'info'); @@ -330,15 +333,24 @@ return view.extend({ 'class': 'btn cbi-button cbi-button-positive important', 'style': 'float:none', 'click': function () { - document.querySelectorAll('.cbi-page-actions button').forEach(function(btn) { - btn.disabled = true; - }) - this.blur(); - this.classList.add('spinning'); + const btn = this; + document.querySelectorAll('.cbi-page-actions button').forEach(function (b) { + b.disabled = true; + }); + btn.blur(); + btn.classList.add('spinning'); L.resolveDefault(fs.exec_direct('/etc/init.d/banip', ['report', 'gen'])) - .then(function () { - location.reload(); - }) + .then(function (res) { + if (res !== null) { + location.reload(); + } else { + btn.classList.remove('spinning'); + document.querySelectorAll('.cbi-page-actions button').forEach(function (b) { + b.disabled = false; + }); + ui.addNotification(null, E('p', _('Failed to generate banIP report!')), 'error'); + } + }); } }, [_('Refresh')]) ])