luci-app-adblock: release 4.5.3-1
authorDirk Brenken <redacted>
Sun, 15 Mar 2026 18:42:25 +0000 (19:42 +0100)
committerDirk Brenken <redacted>
Sun, 15 Mar 2026 18:42:25 +0000 (19:42 +0100)
* sync with base package

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

index b339b6cd781185ee29ad55ce538f4bdb8d269f94..69d3c9142981f3043f5d818a4b3cdaef4d33b4c3 100644 (file)
@@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk
 LUCI_TITLE:=LuCI support for Adblock
 LUCI_DEPENDS:=+luci-base +luci-lib-uqr +adblock
 
-PKG_VERSION:=4.5.2
-PKG_RELEASE:=3
+PKG_VERSION:=4.5.3
+PKG_RELEASE:=1
 PKG_LICENSE:=Apache-2.0
 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
 
index a2e08e267df51c41fcd18d1cc7b3e47d1b93f60f..f7270fe8a1f838f37bc12006aa5dc78ed3c6bc61 100644 (file)
@@ -4,7 +4,8 @@
 'require ui';
 
 const localFile = '/etc/adblock/adblock.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(/[^a-z0-9\.\-# \r\n]/g, '').replace(/\r\n?/g, '\n'));
+
+       handleSave: function (_ev) {
+               const value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/[^a-z0-9.\-# \r\n]/g, '').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 adblock 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 adblock 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 46542bcad5093ccecc2d0ca4ac9fcb8316c494a8..f447c1f68737bf03f60c0b1dea36542729d77858 100644 (file)
@@ -4,7 +4,8 @@
 'require ui';
 
 const localFile = '/etc/adblock/adblock.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');
                }
@@ -32,31 +38,32 @@ return view.extend({
                        E('p', _('This is the local adblock blocklist to always-block certain domains.<br /> \
                                <em><b>Please note:</b></em> add only one domain per line. Comments introduced with \'#\' are allowed - ip addresses, wildcards and regex are not.')),
                        E('textarea', {
-                               'style': 'min-height: 500px; max-height: 90vh; width: 100%; padding: 5px; font-family: monospace; resize: vertical;',
+                               '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(/[^a-z0-9\.\-# \r\n]/g, '').replace(/\r\n?/g, '\n'));
+
+       handleSave: function (_ev) {
+               const value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/[^a-z0-9.\-# \r\n]/g, '').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 adblock 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 adblock 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 8302a5ce99258abfffad8ff35f5279ded62acf32..c11dd96f4d6e68099e5d5ad66c20698d607cb07c 100644 (file)
@@ -4,7 +4,9 @@
 'require ui';
 'require uci';
 
-var notMsg = false, errMsg = false;
+/* separate flags per notification context */
+let listNotMsg = false;
+let mapNotMsg = false;
 
 /*
        button handling
@@ -29,13 +31,13 @@ function handleAction(ev) {
                                        'click': ui.createHandlerFn(this, function (ev) {
                                                L.resolveDefault(fs.read_direct('/etc/adblock/adblock.blocklist'), '')
                                                        .then(function (res) {
-                                                               var domain = document.getElementById('blocklist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g, '');
-                                                               var pattern = new RegExp('^' + domain.replace(/[\.]/g, '\\.') + '$', 'm');
+                                                               const domain = document.getElementById('blocklist').value.trim().toLowerCase().replace(/[^a-z0-9.\-]/g, '');
+                                                               const pattern = new RegExp('^' + domain.replace(/[.]/g, '\\.') + '$', 'm');
                                                                if (res.search(pattern) === -1) {
-                                                                       var blocklist = res + domain + '\n';
+                                                                       const blocklist = res + domain + '\n';
                                                                        fs.write('/etc/adblock/adblock.blocklist', blocklist);
-                                                                       if (!notMsg) {
-                                                                               notMsg = true;
+                                                                       if (!listNotMsg) {
+                                                                               listNotMsg = true;
                                                                                ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload adblock that changes take effect.')), 'info');
                                                                        }
                                                                }
@@ -67,13 +69,13 @@ function handleAction(ev) {
                                        'click': ui.createHandlerFn(this, function (ev) {
                                                L.resolveDefault(fs.read_direct('/etc/adblock/adblock.allowlist'), '')
                                                        .then(function (res) {
-                                                               var domain = document.getElementById('allowlist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g, '');
-                                                               var pattern = new RegExp('^' + domain.replace(/[\.]/g, '\\.') + '$', 'm');
+                                                               const domain = document.getElementById('allowlist').value.trim().toLowerCase().replace(/[^a-z0-9.\-]/g, '');
+                                                               const pattern = new RegExp('^' + domain.replace(/[.]/g, '\\.') + '$', 'm');
                                                                if (res.search(pattern) === -1) {
-                                                                       var allowlist = res + domain + '\n';
+                                                                       const allowlist = res + domain + '\n';
                                                                        fs.write('/etc/adblock/adblock.allowlist', allowlist);
-                                                                       if (!notMsg) {
-                                                                               notMsg = true;
+                                                                       if (!listNotMsg) {
+                                                                               listNotMsg = true;
                                                                                ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload adblock that changes take effect.')), 'info');
                                                                        }
                                                                }
@@ -120,7 +122,7 @@ function handleAction(ev) {
                                E('button', {
                                        'class': 'btn cbi-button-action',
                                        'click': ui.createHandlerFn(this, function (ev) {
-                                               const domain = document.getElementById('search').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g, '');
+                                               const domain = document.getElementById('search').value.trim().toLowerCase().replace(/[^a-z0-9.\-]/g, '');
                                                if (domain) {
                                                        document.getElementById('run').classList.add("spinning");
                                                        document.getElementById('search').value = domain;
@@ -134,7 +136,7 @@ function handleAction(ev) {
                                                                }
                                                                document.getElementById('run').classList.remove("spinning");
                                                                document.getElementById('search').value = '';
-                                                       })
+                                                       });
                                                }
                                                document.getElementById('search').focus();
                                        })
@@ -173,8 +175,7 @@ function handleAction(ev) {
                                ])
                        ]),
                        E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
-                               E('input', { 'class': 'cbi-input-text', 'spellcheck': 'false', 'id': 'search' }, [
-                               ]),
+                               E('input', { 'class': 'cbi-input-text', 'spellcheck': 'false', 'id': 'search' }, []),
                                '\xa0\xa0\xa0',
                                _('Filter criteria like date, domain or client (optional)')
                        ]),
@@ -190,16 +191,16 @@ function handleAction(ev) {
                                        'click': function () {
                                                document.querySelectorAll('.cbi-page-actions button').forEach(function (btn) {
                                                        btn.disabled = true;
-                                               })
+                                               });
                                                this.blur();
                                                this.classList.add('spinning');
                                                const top_count = document.getElementById('top_count').value;
                                                const res_count = document.getElementById('res_count').value;
-                                               const search = document.getElementById('search').value.trim().replace(/[^\w\.\-\:]/g, '') || '+';
+                                               const search = document.getElementById('search').value.trim().replace(/[^\w.\-:]/g, '') || '+';
                                                L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['report', 'gen', top_count, res_count, search]), '')
                                                        .then(function () {
                                                                location.reload();
-                                                       })
+                                                       });
                                        }
                                }, _('Refresh'))
                        ])
@@ -284,30 +285,17 @@ return view.extend({
                        let a_cnt = '\xa0', a_addr = '\xa0', b_cnt = '\xa0', b_addr = '\xa0', c_cnt = '\xa0', c_addr = '\xa0';
                        if (content[0].top_clients[i]) {
                                a_cnt = content[0].top_clients[i].count;
-                       }
-                       if (content[0].top_clients[i]) {
                                a_addr = content[0].top_clients[i].address;
                        }
                        if (content[0].top_domains[i]) {
                                b_cnt = content[0].top_domains[i].count;
-                       }
-                       if (content[0].top_domains[i]) {
                                b_addr = '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].top_domains[i].address) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].top_domains[i].address + '</a>';
                        }
                        if (content[0].top_blocked[i]) {
                                c_cnt = content[0].top_blocked[i].count;
-                       }
-                       if (content[0].top_blocked[i]) {
                                c_addr = '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].top_blocked[i].address) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].top_blocked[i].address + '</a>';
                        }
-                       rows_top.push([
-                               a_cnt,
-                               a_addr,
-                               b_cnt,
-                               b_addr,
-                               c_cnt,
-                               c_addr
-                       ]);
+                       rows_top.push([a_cnt, a_addr, b_cnt, b_addr, c_cnt, c_addr]);
                }
                cbi_update_table(tbl_top, rows_top);
 
@@ -325,11 +313,10 @@ return view.extend({
                        ])
                ]);
 
-               max = 0;
                if (content[0].requests) {
-                       let button;
                        max = content[0].requests.length;
                        for (let i = 0; i < max; i++) {
+                               let button;
                                if (content[0].requests[i].rc === 'NX') {
                                        button = E('button', {
                                                'class': 'btn cbi-button cbi-button-positive',
@@ -365,7 +352,7 @@ return view.extend({
 
                const page = E('div', { 'class': 'cbi-map', 'id': 'map' }, [
                        E('div', { 'class': 'cbi-section' }, [
-                               E('p', _('This tab displays the most recently generated DNS report. Use the \‘Refresh\’ button to update it.')),
+                               E('p', _('This tab displays the most recently generated DNS report. Use the \'Refresh\' button to update it.')),
                                E('div', { 'class': 'cbi-value', 'style': 'position:relative;min-height:220px' }, [
                                        E('div', {
                                                'style': 'position:absolute; top:0; right:0; text-align:center'
@@ -408,7 +395,7 @@ return view.extend({
                                        'style': 'float:none;margin-right:.4em;',
                                        'id': 'btnTest',
                                        'title': 'Adblock Test',
-                                       'click': function() {
+                                       'click': function () {
                                                window.open('https://adblock.turtlecute.org/', '_blank', 'noopener,noreferrer');
                                        }
                                }, [_('Adblock Test')]),
@@ -419,13 +406,12 @@ return view.extend({
                                        'title': 'Map',
                                        '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('map');
-                                               }
-                                               else {
-                                                       if (!notMsg) {
-                                                               notMsg = true;
+                                               } else {
+                                                       if (!mapNotMsg) {
+                                                               mapNotMsg = true;
                                                                return ui.addNotification(null, E('p', _('No GeoIP Map data!')), 'info');
                                                        }
                                                }
@@ -449,40 +435,38 @@ return view.extend({
                                }, [_('Refresh...')])
                        ])
                ]);
+
                if (uci.get('adblock', 'global', 'adb_map') === '1') {
                        const btn = page.querySelector('#btnMap');
                        if (btn) {
                                btn.removeAttribute('disabled');
                        }
                }
+
                /* Draw Pie Chart with Tooltip */
-               const tooltip = E('div', {
+               const tooltipEl = E('div', {
                        id: 'dnsPieTooltip',
                        style: 'position:absolute; padding:6px 10px; background:#333; color:#fff; border-radius:4px; font-size:12px; pointer-events:none; opacity:0; transition:opacity .15s; z-index:9999'
                });
-               document.body.appendChild(tooltip);
-               setTimeout(function() {
+               document.body.appendChild(tooltipEl);
+
+               setTimeout(function () {
                        const total = Number(content[0].total || 0);
                        const blocked = Number(content[0].blocked || 0);
                        const allowed = Math.max(total - blocked, 0);
 
                        const canvas = document.getElementById('dnsPie');
-                       if (!canvas || total <= 0)
-                               return;
+                       if (!canvas || total <= 0) return;
 
                        const ctx = canvas.getContext('2d');
-                       const colors = {
-                               blocked: '#b04a4a',
-                               allowed: '#6a8f6a'
-                       };
+                       const colors = { blocked: '#b04a4a', allowed: '#6a8f6a' };
+                       let finalRot = 0;
 
-                       function drawPie(rotation = 0) {
+                       function drawPie(rotation) {
                                const w = canvas.clientWidth;
                                canvas.width = w;
                                canvas.height = w;
-                               const cx = w / 2;
-                               const cy = w / 2;
-                               const r  = (w / 2) - 4;
+                               const cx = w / 2, cy = w / 2, r = (w / 2) - 4;
                                const blockedAngle = (blocked / total) * 2 * Math.PI;
                                const allowedAngle = (allowed / total) * 2 * Math.PI;
 
@@ -505,43 +489,48 @@ return view.extend({
                                ctx.lineWidth = 2;
                                ctx.stroke();
                        }
+
                        let rot = 0;
                        function animate() {
                                rot += 0.10;
                                drawPie(rot);
-                               if (rot < Math.PI * 2)
+                               if (rot < Math.PI * 2) {
                                        requestAnimationFrame(animate);
+                               } else {
+                                       finalRot = rot % (2 * Math.PI);
+                                       drawPie(finalRot);
+                               }
                        }
                        animate();
-                       window.addEventListener('resize', function() {
-                               drawPie(rot);
+
+                       window.addEventListener('resize', function () {
+                               drawPie(finalRot);
                        });
+
                        const tooltip = document.getElementById('dnsPieTooltip');
-                       canvas.addEventListener('mousemove', function(ev) {
+                       const blockedAngle = (blocked / total) * 2 * Math.PI;
+
+                       canvas.addEventListener('mousemove', function (ev) {
                                const rect = canvas.getBoundingClientRect();
                                const x = ev.clientX - rect.left;
                                const y = ev.clientY - rect.top;
-                               const cx = canvas.width / 2;
-                               const cy = canvas.height / 2;
-                               const dx = x - cx;
-                               const dy = y - cy;
-                               const dist = Math.sqrt(dx*dx + dy*dy);
-                               if (dist > canvas.width/2 - 4) {
+                               const cx = canvas.width / 2, cy = canvas.height / 2;
+                               const dx = x - cx, dy = y - cy;
+                               const dist = Math.sqrt(dx * dx + dy * dy);
+                               if (dist > canvas.width / 2 - 4) {
                                        tooltip.style.opacity = 0;
                                        return;
                                }
-                               let angle = Math.atan2(dy, dx);
+                               /* normalise angle relative to finalRot so it matches the drawn slices */
+                               let angle = Math.atan2(dy, dx) - finalRot;
                                if (angle < 0) angle += 2 * Math.PI;
 
-                               const blockedAngle = (blocked / total) * 2 * Math.PI;
                                let label, abs, pct;
                                if (angle < blockedAngle) {
-                                       label = 'Blocked';
-                                       abs = blocked;
+                                       label = 'Blocked'; abs = blocked;
                                        pct = ((blocked / total) * 100).toFixed(1) + '%';
                                } else {
-                                       label = 'Allowed';
-                                       abs = allowed;
+                                       label = 'Allowed'; abs = allowed;
                                        pct = ((allowed / total) * 100).toFixed(1) + '%';
                                }
                                tooltip.textContent = `${label}: ${abs} (${pct})`;
@@ -549,13 +538,15 @@ return view.extend({
                                tooltip.style.top = ev.pageY + 12 + 'px';
                                tooltip.style.opacity = 1;
                        });
-                       canvas.addEventListener('mouseleave', function() {
+
+                       canvas.addEventListener('mouseleave', function () {
                                tooltip.style.opacity = 0;
                        });
                }, 0);
+
                return page;
        },
        handleSaveApply: null,
        handleSave: null,
        handleReset: null
-});
+});
\ No newline at end of file
index 13d3c6eeebc48e81663bc6ca62dd9747f080b1ff..8dda564ec91d5852643b207521f7d732d30ad3e6 100644 (file)
@@ -13,6 +13,26 @@ document.querySelector('head').appendChild(E('link', {
        'href': L.resource('view/adblock/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/adblock/adblock.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,28 +69,21 @@ function handleEdit(ev) {
        if (ev === 'upload') {
                return ui.uploadFile('/etc/adblock/adblock.custom.feeds').then(function () {
                        L.resolveDefault(fs.read_direct('/etc/adblock/adblock.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/adblock/adblock.custom.feeds', null).then(function () {
-                                                               return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
-                                                       });
+                               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/adblock/adblock.custom.feeds', null).then(function () {
-                                                       return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
+                                               return fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
+                                                       ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
                                                });
                                        }
                                        location.reload();
                                } else {
-                                       fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
-                                               return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
+                                       return fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
+                                               ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
                                        });
                                }
                        });
@@ -118,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();
@@ -142,40 +145,39 @@ function handleEdit(ev) {
                                sub[key] = value;
                        }
                }
-               if (sub.descr) {
-                       sumSubElements.push(keyValue, sub);
+               /* require at least descr and url to produce a valid feed entry */
+               if (sub.descr && sub.url) {
+                       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/adblock/adblock.custom.feeds', exportJson)
                .then(() => location.reload());
 }
 
 return view.extend({
        load: function () {
-               return L.resolveDefault(fs.stat('/etc/adblock/adblock.custom.feeds'), "")
+               return L.resolveDefault(fs.stat('/etc/adblock/adblock.custom.feeds'), null)
                        .then(function (stat) {
-                       if (!stat) {
-                               return fs.write('/etc/adblock/adblock.custom.feeds', "");
-                       }
-                       return L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds', 'json'), "");
-               });
+                               if (!stat) {
+                                       return fs.write('/etc/adblock/adblock.custom.feeds', "").then(function () {
+                                               return { size: 0, data: null };
+                                       });
+                               }
+                               return L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds', 'json'), "")
+                                       .then(function (data) {
+                                               return { size: stat.size, data: data };
+                                       });
+                       });
        },
 
-       render: function (data) {
+       render: function (result) {
                let m, s, o, feed, url, rule, size, descr;
+               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/adblock/adblock.custom.feeds\'. \
@@ -209,7 +211,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;
@@ -219,7 +221,7 @@ return view.extend({
                        o.value('feed 1', _('<Domain>'));
                        o.value('feed 127.0.0.1 2', _('127.0.0.1 <Domain>'));
                        o.value('feed 0.0.0.0 2', _('0.0.0.0 <Domain>'));
-                       o.value('feed 3 [|^]', _('<Adblock Plus Syntax>'));
+                       o.value('feed || 3 [|^]', _('<Adblock Plus Syntax>'));
                        o.optional = true;
                        o.rmempty = true;
 
@@ -294,11 +296,11 @@ return view.extend({
                                                return handleEdit('save');
                                        })
                                }, [_('Save')]),
-                       ])
+                       ]);
                });
                return m.render();
        },
        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 d883aa513f4226cd31cdec0c73d19aa771782183..e496a41d3cd84121f936d08b35aff6f528e3f454 100644 (file)
@@ -24,7 +24,7 @@ function handleAction(ev) {
                                        btn.blur();
                                });
                                return fs.exec_direct('/etc/init.d/adblock', [ev]);
-                       })
+                       });
        } else {
                if (ev !== 'stop') {
                        document.querySelectorAll('.cbi-page-actions button').forEach(function (btn) {
@@ -49,6 +49,7 @@ return view.extend({
                        `https://${window.location.hostname}/cgi-bin/adblock`
                ]);
        },
+
        render: function (result) {
                /*
                        config check
@@ -78,6 +79,7 @@ return view.extend({
                /*
                        poll runtime information
                */
+               let parseErrCount = 0;
                poll.add(function () {
                        return L.resolveDefault(fs.stat('/var/run/adb_runtime.json'), null).then(function (stat) {
                                if (!stat) {
@@ -89,15 +91,17 @@ return view.extend({
                                        let info = null;
                                        try {
                                                info = JSON.parse(res);
+                                               parseErrCount = 0;
                                        } catch (e) {
                                                info = null;
+                                               parseErrCount++;
                                                if (status) {
                                                        status.textContent = '-';
-                                                       if (status.classList.contains('spinning')) {
-                                                               buttons.forEach(function (btn) {
-                                                                       btn.disabled = false;
-                                                               })
-                                                               status.classList.remove('spinning');
+                                                       buttons.forEach(function (btn) {
+                                                               btn.disabled = false;
+                                                       });
+                                                       status.classList.remove('spinning');
+                                                       if (parseErrCount >= 3) {
                                                                ui.addNotification(null, E('p', _('Unable to parse the adblock runtime information!')), 'error');
                                                                poll.stop();
                                                        }
@@ -110,7 +114,7 @@ return view.extend({
                                                        buttons.forEach(function (btn) {
                                                                btn.disabled = true;
                                                                btn.blur();
-                                                       })
+                                                       });
                                                        if (!status.classList.contains("spinning")) {
                                                                status.classList.add("spinning");
                                                        }
@@ -126,7 +130,7 @@ return view.extend({
                                                        }
                                                        buttons.forEach(function (btn) {
                                                                btn.disabled = false;
-                                                       })
+                                                       });
                                                }
                                        }
                                        if (info) {
@@ -180,7 +184,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')),
@@ -224,7 +228,7 @@ return view.extend({
                o.rmempty = true;
 
                o = s.taboption('general', form.Flag, 'adb_tld', _('TLD Compression'), _('The top level domain compression removes thousands of needless host entries from the final DNS blocklist.'));
-               o.default = 1
+               o.default = 1;
                o.rmempty = true;
 
                o = s.taboption('general', form.Flag, 'adb_safesearch', _('Enable SafeSearch'), _('Enforcing SafeSearch for google, bing, brave, duckduckgo, yandex, youtube and pixabay.'));
@@ -303,13 +307,13 @@ return view.extend({
                o.rmempty = true;
 
                o = s.taboption('additional', form.Flag, 'adb_fetchinsecure', _('Download Insecure'), _('Don\'t check SSL server certificates during download.'));
-               o.default = 0
+               o.default = 0;
                o.rmempty = true;
 
                /*
                        firewall settings tab
                */
-               o = s.taboption('firewall', form.DummyValue, '_sub');
+               o = s.taboption('firewall', form.DummyValue, '_fw_sub1');
                o.rawhtml = true;
                o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service restart to take effect.') + '</em>'
                        + '<hr style="width: 200px; height: 1px;" />'
@@ -357,7 +361,7 @@ return view.extend({
                o.default = '2a13:1001::86:54:11:100';
                o.rmempty = true;
 
-               o = s.taboption('firewall', form.DummyValue, '_sub');
+               o = s.taboption('firewall', form.DummyValue, '_fw_sub2');
                o.rawhtml = true;
                o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('External Filtered DNS Policy (MAC-/Interface‑based DNS bypass)') + '</em>';
 
@@ -421,7 +425,7 @@ return view.extend({
                o.default = '2a13:1001::86:54:11:13';
                o.rmempty = true;
 
-               o = s.taboption('firewall', form.DummyValue, '_sub');
+               o = s.taboption('firewall', form.DummyValue, '_fw_sub3');
                o.rawhtml = true;
                o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('External Remote DNS Policy (temporary MAC‑based remote DNS bypass)') + '</em>';
 
@@ -481,12 +485,12 @@ return view.extend({
                                blackColor: 'black'
                        };
                        const svg = uqr.renderSVG(url, options);
-                       o = s.taboption('firewall', form.DummyValue, '_sub', _('QRCode for Remote Access'));
+                       o = s.taboption('firewall', form.DummyValue, '_fw_qr', _('QRCode for Remote Access'));
                        o.rawhtml = true;
                        o.default = svg;
                }
 
-               o = s.taboption('firewall', form.DummyValue, '_sub');
+               o = s.taboption('firewall', form.DummyValue, '_fw_sub4');
                o.rawhtml = true;
                o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('External DNS Bridge (Zero‑Downtime during DNS Restarts)') + '</em>';
 
@@ -547,7 +551,7 @@ return view.extend({
                o.default = '2a13:1001::86:54:11:13';
                o.rmempty = true;
 
-               o = s.taboption('firewall', form.DummyValue, '_sub');
+               o = s.taboption('firewall', form.DummyValue, '_fw_sub5');
                o.rawhtml = true;
                o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('Local DNS Enforcement') + '</em>';
 
@@ -721,12 +725,9 @@ return view.extend({
                /*
                        prepare category data
                */
-               var code, category, list, path, categories = [];
-               if (result[2]) {
-                       categories = result[2].trim().split('\n');
-               }
+               const categories = result[2] ? result[2].trim().split('\n') : [];
 
-               o = s.taboption('feeds', form.DummyValue, '_sub');
+               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;">' + _('1Hosts List Selection') + '</em>';
 
@@ -734,19 +735,14 @@ return view.extend({
                for (let i = 0; i < categories.length; i++) {
                        const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
                        if (!cat) continue;
-
-                       const code = cat[1].trim();
-                       const list = cat[2].trim();
-                       const path = cat[3].trim();
-
-                       if (code === 'hst') {
-                               o.value(path, list);
+                       if (cat[1].trim() === 'hst') {
+                               o.value(cat[3].trim(), cat[2].trim());
                        }
                }
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('feeds', form.DummyValue, '_sub');
+               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;">' + _('Hagezi List Selection') + '</em>';
 
@@ -754,19 +750,14 @@ return view.extend({
                for (let i = 0; i < categories.length; i++) {
                        const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
                        if (!cat) continue;
-
-                       const code = cat[1].trim();
-                       const list = cat[2].trim();
-                       const path = cat[3].trim();
-
-                       if (code === 'hag') {
-                               o.value(path, list);
+                       if (cat[1].trim() === 'hag') {
+                               o.value(cat[3].trim(), cat[2].trim());
                        }
                }
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('feeds', form.DummyValue, '_sub');
+               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;">' + _('IPFire List Selection') + '</em>';
 
@@ -774,19 +765,14 @@ return view.extend({
                for (let i = 0; i < categories.length; i++) {
                        const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
                        if (!cat) continue;
-
-                       const code = cat[1].trim();
-                       const list = cat[2].trim();
-                       const path = cat[3].trim();
-
-                       if (code === 'ipf') {
-                               o.value(path, list);
+                       if (cat[1].trim() === 'ipf') {
+                               o.value(cat[3].trim(), cat[2].trim());
                        }
                }
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('feeds', form.DummyValue, '_sub');
+               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;">' + _('StevenBlack List Selection') + '</em>';
 
@@ -794,19 +780,14 @@ return view.extend({
                for (let i = 0; i < categories.length; i++) {
                        const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
                        if (!cat) continue;
-
-                       const code = cat[1].trim();
-                       const list = cat[2].trim();
-                       const path = cat[3].trim();
-
-                       if (code === 'stb') {
-                               o.value(path, list);
+                       if (cat[1].trim() === 'stb') {
+                               o.value(cat[3].trim(), cat[2].trim());
                        }
                }
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('feeds', form.DummyValue, '_sub');
+               o = s.taboption('feeds', form.DummyValue, '_feeds5');
                o.rawhtml = true;
                o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('UTCapitole Archive Selection') + '</em>';
 
@@ -814,12 +795,8 @@ return view.extend({
                for (let i = 0; i < categories.length; i++) {
                        const cat = categories[i].match(/^(\w+);(.*)$/);
                        if (!cat) continue;
-
-                       const code = cat[1].trim();
-                       const category = cat[2].trim();
-
-                       if (code === 'utc') {
-                               o.value(category);
+                       if (cat[1].trim() === 'utc') {
+                               o.value(cat[2].trim());
                        }
                }
                o.optional = true;
@@ -861,14 +838,14 @@ return view.extend({
                                        'style': 'float:none',
                                        'title': 'Save & Restart',
                                        'click': function () {
-                                               handleAction('restart');
+                                               return handleAction('restart');
                                        }
                                }, [_('Save & Restart')])
-                       ])
+                       ]);
                });
                return m.render();
        },
        handleSaveApply: null,
        handleSave: null,
        handleReset: null
-});
+});
\ No newline at end of file
git clone https://git.99rst.org/PROJECT