diff --git a/applications/luci-app-adblock/Makefile b/applications/luci-app-adblock/Makefile index b339b6cd78..69d3c91429 100644 --- a/applications/luci-app-adblock/Makefile +++ b/applications/luci-app-adblock/Makefile @@ -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 diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/allowlist.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/allowlist.js index a2e08e267d..f7270fe8a1 100644 --- a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/allowlist.js +++ b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/allowlist.js @@ -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 diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blocklist.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blocklist.js index 46542bcad5..f447c1f687 100644 --- a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blocklist.js +++ b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blocklist.js @@ -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.
\ Please note: 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 diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/dnsreport.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/dnsreport.js index 8302a5ce99..c11dd96f4d 100644 --- a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/dnsreport.js +++ b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/dnsreport.js @@ -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 = '' + content[0].top_domains[i].address + ''; } if (content[0].top_blocked[i]) { c_cnt = content[0].top_blocked[i].count; - } - if (content[0].top_blocked[i]) { c_addr = '' + content[0].top_blocked[i].address + ''; } - 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 diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js index 13d3c6eeeb..8dda564ec9 100644 --- a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js +++ b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js @@ -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', _('')); o.value('feed 127.0.0.1 2', _('127.0.0.1 ')); o.value('feed 0.0.0.0 2', _('0.0.0.0 ')); - o.value('feed 3 [|^]', _('')); + o.value('feed || 3 [|^]', _('')); 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 diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/logtemplate.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/logtemplate.js index c26aa23bbd..9ac4d6752c 100644 --- a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/logtemplate.js +++ b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/logtemplate.js @@ -12,37 +12,39 @@ function Logview(logtag, name) { return L.view.extend({ load: () => Promise.resolve(), - render: () => { - L.Poll.add(() => { + render: function () { + const pollFn = () => { return callLogRead(1000, false, true).then(res => { const logEl = document.getElementById('logfile'); if (!logEl) return; const filtered = (res?.log ?? []) - .filter(entry => !logtag || entry.msg.includes(logtag)) - .map(entry => { - const d = new Date(entry.time); - const date = d.toLocaleDateString([], { - year: 'numeric', - month: '2-digit', - day: '2-digit' + .filter(entry => !logtag || entry.msg.includes(logtag)) + .map(entry => { + const d = new Date(entry.time * 1000); + const date = d.toLocaleDateString([], { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }); + const time = d.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + return `[${date}-${time}] ${entry.msg}`; }); - const time = d.toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - }); - return `[${date}-${time}] ${entry.msg}`; - }); - if (filtered.length > 0) { - logEl.value = filtered.join('\n'); - } else { - logEl.value = _('No %s related logs yet!').format(name); - } + + logEl.value = filtered.length > 0 + ? filtered.join('\n') + : _('No %s related logs yet!').format(name); logEl.scrollTop = logEl.scrollHeight; }); - }); + }; + + this._pollFn = pollFn; + L.Poll.add(pollFn); return E('div', { class: 'cbi-map' }, [ E('div', { class: 'cbi-section' }, [ @@ -58,10 +60,17 @@ function Logview(logtag, name) { ]); }, + unload: function () { + if (this._pollFn) { + L.Poll.remove(this._pollFn); + this._pollFn = null; + } + }, + handleSaveApply: null, handleSave: null, handleReset: null }); } -return L.Class.extend({ Logview }); +return L.Class.extend({ Logview }); \ No newline at end of file diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js index d883aa513f..e496a41d3c 100644 --- a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js +++ b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js @@ -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 = '' + _('Changes on this tab needs an adblock service restart to take effect.') + '' + '
' @@ -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 = '
' + _('External Filtered DNS Policy (MAC-/Interface‑based DNS bypass)') + ''; @@ -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 = '
' + _('External Remote DNS Policy (temporary MAC‑based remote DNS bypass)') + ''; @@ -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 = '
' + _('External DNS Bridge (Zero‑Downtime during DNS Restarts)') + ''; @@ -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 = '
' + _('Local DNS Enforcement') + ''; @@ -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 = '
' + _('1Hosts List Selection') + ''; @@ -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 = '
' + _('Hagezi List Selection') + ''; @@ -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 = '
' + _('IPFire List Selection') + ''; @@ -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 = '
' + _('StevenBlack List Selection') + ''; @@ -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 = '
' + _('UTCapitole Archive Selection') + ''; @@ -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