luci-app-adblock: sync with adblock 4.5.0-2
authorDirk Brenken <dev@brenken.org>
Sat, 24 Jan 2026 13:39:03 +0000 (14:39 +0100)
committerDirk Brenken <dev@brenken.org>
Sat, 24 Jan 2026 13:39:03 +0000 (14:39 +0100)
Signed-off-by: Dirk Brenken <dev@brenken.org>
applications/luci-app-adblock/Makefile
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/dnsreport.js
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js

index 516c404e7a06e54ebf32653e9b3755b9acb2ea9f..264b2c954953fd2a945a5d6a91621e74d4b399ca 100644 (file)
@@ -7,7 +7,7 @@ LUCI_TITLE:=LuCI support for Adblock
 LUCI_DEPENDS:=+luci-base +adblock
 
 PKG_VERSION:=4.5.0
-PKG_RELEASE:=1
+PKG_RELEASE:=2
 PKG_LICENSE:=Apache-2.0
 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
 
index c41a4907000d1097df77deb2f4fddcd5cd209bda..e79dccfb48fa4bc5b12c28f35e2ff351b29e1f19 100644 (file)
@@ -317,6 +317,7 @@ return view.extend({
                                E('th', { 'class': 'th' }, _('Date')),
                                E('th', { 'class': 'th' }, _('Time')),
                                E('th', { 'class': 'th' }, _('Client')),
+                               E('th', { 'class': 'th' }, _('Type')),
                                E('th', { 'class': 'th' }, _('Domain')),
                                E('th', { 'class': 'th' }, _('Answer')),
                                E('th', { 'class': 'th' }, _('Action'))
@@ -351,6 +352,7 @@ return view.extend({
                                        content[0].requests[i].date,
                                        content[0].requests[i].time,
                                        content[0].requests[i].client,
+                                       content[0].requests[i].type,
                                        '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].requests[i].domain) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].requests[i].domain + '</a>',
                                        content[0].requests[i].rc,
                                        button
@@ -361,35 +363,40 @@ return view.extend({
 
                const page = E('div', { 'class': 'cbi-map', 'id': 'map' }, [
                        E('div', { 'class': 'cbi-section' }, [
-                               E('p', _('This tab shows the last generated DNS Report, press the \'Refresh\' button to get a current one.')),
-                               E('p', '\xa0'),
-                               E('div', { 'class': 'cbi-value' }, [
-                                       E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('Start Timestamp')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'float:left;color:#37c' }, (content[0].start_date || '-') + ', ' + (content[0].start_time || '-'))
-                               ]),
-                               E('div', { 'class': 'cbi-value' }, [
-                                       E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('End Timestamp')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'end', 'style': 'float:left;color:#37c' }, (content[0].end_date || '-') + ', ' + (content[0].end_time || '-'))
-                               ]),
-                               E('div', { 'class': 'cbi-value' }, [
-                                       E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('Total DNS Requests')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'total', 'style': 'float:left;color:#37c' }, content[0].total || '-')
-                               ]),
-                               E('div', { 'class': 'cbi-value' }, [
-                                       E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('Blocked DNS Requests')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'blocked', 'style': 'float:left;color:#37c' }, (content[0].blocked || '-') + ' (' + (content[0].percent || '-') + ')')
+                               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'
+                                       }, [
+                                               E('div', { 'style': 'font-size:12px; color:#37c; margin-bottom:8px; font-family:monospace; line-height:1.3; text-align:right' }, [
+                                                       E('div', { 'style': 'font-size:12px; color:#37c; margin-bottom:8px' }, [
+                                                               E('div', {}, 'Start: ' + (content[0].start_date || '-') + ' ' + (content[0].start_time || '-')),
+                                                               E('div', {}, 'End: ' + (content[0].end_date || '-') + ' ' + (content[0].end_time || '-'))
+                                                       ])
+                                               ]),
+                                               E('canvas', {
+                                                       'id': 'dnsPie',
+                                                       'width': 160,
+                                                       'height': 160,
+                                                       'style': 'max-width:160px; width:28vw; height:auto; cursor:pointer;'
+                                               }),
+                                               E('div', { 'style': 'margin-top:5px; font-size:12px' }, [
+                                                       E('span', { 'style': 'color:#b04a4a' }, '■ Blocked'),
+                                                       E('span', { 'style': 'margin-left:10px;color:#6a8f6a' }, '■ Allowed')
+                                               ])
+                                       ])
                                ])
                        ]),
                        E('div', { 'class': 'cbi-section' }, [
                                E('div', { 'class': 'left' }, [
-                                       E('h3', _('Top Statistics')),
+                                       E('h3', { 'style': 'white-space:nowrap' }, _('Top Statistics')),
                                        tbl_top
                                ])
                        ]),
                        E('br'),
                        E('div', { 'class': 'cbi-section' }, [
                                E('div', { 'class': 'left' }, [
-                                       E('h3', _('Latest DNS Requests')),
+                                       E('h3', { 'style': 'white-space:nowrap' }, _('Latest DNS Requests')),
                                        tbl_requests
                                ])
                        ]),
@@ -446,6 +453,105 @@ return view.extend({
                                btn.removeAttribute('disabled');
                        }
                }
+               /* Draw Pie Chart with Tooltip */
+               const tooltip = 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() {
+                       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;
+
+                       const ctx = canvas.getContext('2d');
+                       const colors = {
+                               blocked: '#b04a4a',
+                               allowed: '#6a8f6a'
+                       };
+
+                       function drawPie(rotation = 0) {
+                               const w = canvas.clientWidth;
+                               canvas.width = w;
+                               canvas.height = w;
+                               const cx = w / 2;
+                               const cy = w / 2;
+                               const r  = (w / 2) - 4;
+                               const blockedAngle = (blocked / total) * 2 * Math.PI;
+                               const allowedAngle = (allowed / total) * 2 * Math.PI;
+
+                               ctx.clearRect(0, 0, w, w);
+                               ctx.beginPath();
+                               ctx.moveTo(cx, cy);
+                               ctx.fillStyle = colors.blocked;
+                               ctx.arc(cx, cy, r, rotation, rotation + blockedAngle);
+                               ctx.fill();
+
+                               ctx.beginPath();
+                               ctx.moveTo(cx, cy);
+                               ctx.fillStyle = colors.allowed;
+                               ctx.arc(cx, cy, r, rotation + blockedAngle, rotation + blockedAngle + allowedAngle);
+                               ctx.fill();
+
+                               ctx.beginPath();
+                               ctx.arc(cx, cy, r, 0, 2 * Math.PI);
+                               ctx.strokeStyle = '#fff';
+                               ctx.lineWidth = 2;
+                               ctx.stroke();
+                       }
+                       let rot = 0;
+                       function animate() {
+                               rot += 0.10;
+                               drawPie(rot);
+                               if (rot < Math.PI * 2)
+                                       requestAnimationFrame(animate);
+                       }
+                       animate();
+                       window.addEventListener('resize', function() {
+                               drawPie(rot);
+                       });
+
+                       const tooltip = document.getElementById('dnsPieTooltip');
+                       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) {
+                                       tooltip.style.opacity = 0;
+                                       return;
+                               }
+                               let angle = Math.atan2(dy, dx);
+                               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;
+                                       pct = ((blocked / total) * 100).toFixed(1) + '%';
+                               } else {
+                                       label = 'Allowed';
+                                       abs = allowed;
+                                       pct = ((allowed / total) * 100).toFixed(1) + '%';
+                               }
+                               tooltip.textContent = `${label}: ${abs} (${pct})`;
+                               tooltip.style.left = ev.pageX + 12 + 'px';
+                               tooltip.style.top = ev.pageY + 12 + 'px';
+                               tooltip.style.opacity = 1;
+                       });
+                       canvas.addEventListener('mouseleave', function() {
+                               tooltip.style.opacity = 0;
+                       });
+               }, 0);
                return page;
        },
        handleSaveApply: null,
index c87eb38a56996046050c4a284d52892c19b17556..badee722beff463575ec22329973cb0683714607 100644 (file)
@@ -502,8 +502,11 @@ return view.extend({
                o.datatype = 'range(1,10)';
                o.rmempty = true;
 
-               o = s.taboption('adv_report', form.Value, 'adb_replisten', _('Report Ports'), _('Space separated list of ports used by tcpdump.'));
+               o = s.taboption('adv_report', form.DynamicList, 'adb_repport', _('Report Ports'), _('The list of ports used by tcpdump.'));
+               o.datatype = 'port';
                o.placeholder = '53';
+               o.multiple = true;
+               o.optional = true;
                o.rmempty = true;
 
                o = s.taboption('adv_report', form.Flag, 'adb_represolve', _('Resolve IPs'), _('Resolve reporting IP addresses by using reverse DNS (PTR) lookups.'));