luci-app-banip: release 1.8.1-1
authorDirk Brenken <redacted>
Sun, 15 Mar 2026 17:21:18 +0000 (18:21 +0100)
committerDirk Brenken <redacted>
Sun, 15 Mar 2026 17:21:18 +0000 (18:21 +0100)
* sync with base package

Signed-off-by: Dirk Brenken <redacted>
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js
applications/luci-app-banip/Makefile
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/allowlist.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/blocklist.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/feeds.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/logtemplate.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/overview.js
applications/luci-app-banip/htdocs/luci-static/resources/view/banip/setreport.js

index 7640d39dc0cbc804eeca23c79b3842c9e060664b..d883aa513f4226cd31cdec0c73d19aa771782183 100644 (file)
@@ -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')),
index 3a494c5091e968ecaeffb006296568c143b63eef..5231e2ea1e5e1f6c8a803ab971b6d346ca71bb60 100644 (file)
@@ -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 <dev@brenken.org>
 
index 678e3c2389039c8c81dbfc0027b52b2821486ee1..30ba7038dba34ce4fbcb59e25bac3950dc6a7444 100644 (file)
@@ -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
index ad5c3fd39e3e90ef0ec2769534f0d0317613f527..9832f2c9525dd34e3593ad2842574fc2112fe5e1 100644 (file)
@@ -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
index 25ad62693c286c0d2fb0d13b61317343266dab8a..11a629d64d72115b8411b9dccc89f8f9f967e806 100644 (file)
@@ -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
index c26aa23bbdff91d61c2cd09f7c91d17a58e070f6..9ac4d6752ca7bf8280e6e3fb843bbd73aa7479aa 100644 (file)
@@ -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
index b3d119ec8b9e6ca6f0bb1776f33e6ca4bad73854..1ee1bed1d341659bf2039a82b5aa3ea3e566ce1b 100644 (file)
@@ -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 (info) {
-                                       setText('elements', info.element_count);
-                                       setText('feeds', info.active_feeds?.join(', '));
-                                       setText('devices', `wan-dev: ${info.wan_devices?.join(', ') || '-'} /
+                                       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(', ') || '-'} /
                                                        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 = '<em style="color:#37c;font-weight:bold;">' + _('To enable email notifications, set up the \'msmtp\' package and specify a vaild E-Mail receiver address.') + '</em>'
+               o.default = '<em style="color:#37c;font-weight:bold;">' + _('To enable email notifications, set up the \'msmtp\' package and specify a valid E-Mail receiver address.') + '</em>'
                        + '<hr style="width: 200px; height: 1px;" />';
 
                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 = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('Country Selection') + '</em>';
 
@@ -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 = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('ASN Selection') + '</em>';
 
@@ -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 = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('External Allowlist Feeds') + '</em>';
 
@@ -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 = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('Local Feed Settings') + '</em>';
 
                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();
        },
index 8768cbab8ea0c4ee3b9c610e6b6d285f096ad047..1f59bd121593ca5ba221d93a0c61c4838fbf07a8 100644 (file)
@@ -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')])
                        ])
git clone https://git.99rst.org/PROJECT