luci-app-antiblock: Update luci-app-antiblock package

1) Added the ability to route different domains through different gateways, up to 32 routes.
2) The program has been switched from proxying mode to sniffer mode.
3) Blacklist has been added so that the specified subnets are not added to the routing table.
4) Added the logs and statistics tabs
5) The "output" option has been removed, it is now /tmp/antiblock
6) Bash backend removed, now only JS.

Signed-off-by: Khachatryan Karen <karen0734@gmail.com>
This commit is contained in:
Khachatryan Karen
2025-03-21 14:29:32 +03:00
committed by Paul Donald
parent 6c061b644c
commit 601a69f2fe
10 changed files with 485 additions and 225 deletions

View File

@@ -0,0 +1,26 @@
'use strict';
'require view';
'require form';
'require tools.widgets as widgets';
return view.extend({
render: function () {
const m = new form.Map('antiblock', _('AntiBlock'));
const s = m.section(form.NamedSection, 'config', 'main', _('AntiBlock'));
s.addremove = true;
let o = s.option(form.Flag, 'enabled', _('Enabled'));
o = s.option(form.DynamicList, 'blacklist', _('Blacklist'), _('Prevent adding IP from these subnets to the routing table, optional parameter'));
o.depends('enabled', '1');
o = s.option(form.Flag, 'log', _('Log'), _('Show operations log, optional parameter'));
o.depends('enabled', '1');
o = s.option(form.Flag, 'stat', _('Statistics'), _('Show statistics data, optional parameter'));
o.depends('enabled', '1');
return m.render();
},
});

View File

@@ -1,107 +1,109 @@
'use strict'; 'use strict';
'require ui'; 'require ui';
'require uci';
'require fs';
'require form'; 'require form';
'require rpc';
'require view'; 'require view';
const read_domains = rpc.declare({ let section_routes;
object: 'luci.antiblock', let section_data;
method: 'read_domains' let domains_textarea;
});
const write_domains = rpc.declare({ async function write_domains_handler() {
object: 'luci.antiblock', ui.showModal(null, [E('p', { class: 'spinning' }, _('Write domains'))]);
method: 'write_domains', const lines = domains_textarea.value.split(/\r?\n/).filter(Boolean);
params: ['domains'] let write_data = '';
}); lines.forEach(function (element) { write_data += element + '\n' });
const domains_path = section_routes.selectedOptions[0].label;
try {
await fs.write(domains_path, write_data);
await fs.exec('/etc/init.d/antiblock', ['restart']);
} catch (err) {
ui.addNotification(null, E('p', {}, _('Unable to write to domains file') + ' ' + domains_path + ' "' + err.message + '"'));
}
ui.hideModal();
select_handler();
}
function read_domains_handler(data) {
const text_data = data.split(/\r?\n/).filter(Boolean);
const section_descr_div = E('div', { class: 'cbi-section-descr' }, _('Domain count in file:') + ' ' + text_data.length);
domains_textarea = E('textarea', { class: 'cbi-input-textarea' },);
domains_textarea.value = '';
text_data.forEach(function (element) { domains_textarea.value += element + '\n' });
const btn_write_domains = E('button', { class: 'cbi-button cbi-button-apply', click: write_domains_handler }, _('Write domains'));
const div_for_btn = E('div', { style: 'padding-top: 20px' });
div_for_btn.appendChild(btn_write_domains);
section_data.innerHTML = '';
section_data.appendChild(section_descr_div);
section_data.appendChild(domains_textarea);
section_data.appendChild(div_for_btn);
}
function select_handler() {
section_data.innerHTML = '';
const domains_path = section_routes.selectedOptions[0].label;
fs.read_direct(domains_path).then(
read_domains_handler
).catch(
function (err) {
if (err.message == 'Failed to stat requested path') {
fs.exec('/bin/mkdir', ['/etc/antiblock']).then(
fs.write(domains_path, '').then(
read_domains_handler("")
).catch(
function (err) {
section_data.appendChild(E('p', {}, _('Unable to create domains file') + ' ' + domains_path + ' "' + err.message + '"'));
}
)
);
} else {
section_data.appendChild(E('p', {}, _('Unable to read domains file') + ' ' + domains_path + ' "' + err.message + '"'));
}
}
);
}
return view.extend({ return view.extend({
generic_failure: function (message) { handleSaveApply: null,
return E('div', { handleSave: null,
'class': 'error' handleReset: null,
}, ['RPC call failure: ', message]); load: async function () {
return await uci.load('antiblock');
}, },
load: function () { render: function () {
return Promise.all([ const uci_routes = uci.sections('antiblock', 'route');
read_domains()
]);
},
render: function (data) {
const main_div = E('div');
const header = E('h2', {}, _('AntiBlock')); section_routes = E('select', { class: 'cbi-input-select', change: select_handler });
uci_routes.forEach(function (route) {
const section_descr_div = E( if (route.domains_path.substring(0, 4) != 'http') {
'div', const routes_option = E('option', { value: route.domains_path }, route.domains_path);
{ section_routes.appendChild(routes_option);
class: 'cbi-section-descr',
},
_('Domains count in file: ')
);
const section_div = E(
'div',
{
class: 'cbi-section',
} }
); });
main_div.appendChild(header); const main_div = E([]);
main_div.appendChild(section_div); main_div.appendChild(E('h2', _('Domains')));
section_div.appendChild(section_descr_div);
if (typeof data[0].domains !== 'undefined') { if (section_routes.innerHTML != '') {
const domains_textarea = E( const routes_div = E('div', { class: 'cbi-section' });
'textarea', routes_div.appendChild(E('div', { class: 'cbi-section-descr' }, _('Domains path:')));
{ routes_div.appendChild(section_routes);
class: 'cbi-input-textarea', main_div.appendChild(routes_div);
},
);
section_descr_div.innerHTML += data[0].domains.length; section_data = E('div', { class: 'cbi-section' });
main_div.appendChild(section_data);
domains_textarea.value = ''; select_handler();
data[0].domains.forEach((element) => domains_textarea.value += element + '\n');
const btn_write_domains = E(
'button',
{
class: 'btn cbi-button cbi-button-apply',
click: function (ev) {
ui.showModal(null, [
E(
'p',
{ class: 'spinning' },
_('Write domains')
),
]);
const lines = domains_textarea.value.split(/\r?\n/).filter(Boolean);
const write_domains_res = Promise.all([write_domains(lines)]);
write_domains_res.then(
function (value) { location.reload(); },
function (error) { /* code if some error */ }
);
},
},
_('Write domains')
);
section_div.appendChild(domains_textarea);
section_div.appendChild(btn_write_domains);
} else { } else {
const error_div = E( const routes_div = E('div', { class: 'cbi-section' });
'div', routes_div.appendChild(E('div', { class: 'cbi-section-descr' }, _('Path to file in "Domains path" is not set.')));
{ main_div.appendChild(routes_div);
},
_('The File argument was not specified.')
);
section_div.appendChild(error_div);
} }
return main_div; return main_div;
}, }
handleSave: null,
handleSaveApply: null,
handleReset: null
}); });

View File

@@ -1,44 +0,0 @@
'use strict';
'require view';
'require form';
'require tools.widgets as widgets';
return view.extend({
render: function () {
const m = new form.Map('antiblock', _('AntiBlock'));
const s = m.section(form.NamedSection, 'config', 'antiblock', _('AntiBlock'));
s.addremove = true;
let o = s.option(form.Flag, 'enabled', _('Enabled'));
o = s.option(form.Value, 'url', _('URL'), _('Domains file URL, either File or URL or both'));
o.default = 'https://antifilter.download/list/domains.lst';
o.depends('enabled', '1');
o = s.option(form.Value, 'file', _('File'), _('Domains file path, either File or URL or both'));
o.depends('enabled', '1');
o = s.option(form.Value, 'DNS', _('DNS'), _('DNS address, required parameter'));
o.default = '1.1.1.1:53';
o.depends('enabled', '1');
o = s.option(form.Value, 'listen', _('Listen'), _('Listen address, required parameter'));
o.default = '192.168.1.1:5053';
o.depends('enabled', '1');
o = s.option(widgets.DeviceSelect, 'VPN_name', _('VPN name'), _('Interface name, required parameter'));
o.depends('enabled', '1');
o = s.option(form.Value, 'output', _('Output'), _('Log or statistics output folder, optional parameter'));
o.depends('enabled', '1');
o = s.option(form.Flag, 'log', _('Log'), _('Show operations log, optional parameter'));
o.depends({ output: '/', '!contains': true });
o = s.option(form.Flag, 'stat', _('Stat'), _('Show statistics data, optional parameter'));
o.depends({ output: '/', '!contains': true });
return m.render();
},
});

View File

@@ -0,0 +1,91 @@
'use strict';
'require view';
'require fs';
'require poll';
'require ui';
'require uci';
let main_config;
return view.extend({
retrieveLog: async function () {
return fs.read_direct('/tmp/antiblock/log.txt').then(function (logdata) {
const loglines = logdata.trim().split(/\n/).map(function (line) {
return line.replace(/^<\d+>/, '');
});
return { value: loglines.join('\n'), rows: loglines.length + 1 };
}).catch(function (err) {
ui.addNotification(null, E('p', {}, _('Unable to load log data:') + ' ' + err.message));
return '';
});
},
pollLog: async function () {
const element = document.getElementById('syslog');
if (element) {
const log = await this.retrieveLog();
element.value = log.value;
element.rows = log.rows;
}
},
load: async function () {
await uci.load('antiblock');
main_config = uci.sections('antiblock', 'main');
if (!main_config[0]?.log || main_config[0]?.log === '0') {
return;
}
poll.add(this.pollLog.bind(this), 10);
return await this.retrieveLog();
},
render: function (loglines) {
const main_div = E([]);
main_div.appendChild(E('h2', _('Log')));
const routes_div = E('div', { class: 'cbi-section' });
routes_div.appendChild(E('div', { class: 'cbi-section-descr' }, _('Log is not enabled.')));
main_div.appendChild(routes_div);
if (!main_config[0]?.log || main_config[0]?.log === '0') {
return main_div;
}
const scrollDownButton = E('button', {
'id': 'scrollDownButton',
'class': 'cbi-button cbi-button-neutral',
}, _('Scroll to tail', 'scroll to bottom (the tail) of the log file'));
scrollDownButton.addEventListener('click', function () {
scrollUpButton.scrollIntoView();
});
const scrollUpButton = E('button', {
'id': 'scrollUpButton',
'class': 'cbi-button cbi-button-neutral',
}, _('Scroll to head', 'scroll to top (the head) of the log file'));
scrollUpButton.addEventListener('click', function () {
scrollDownButton.scrollIntoView();
});
return E([], [
E('h2', {}, [_('Log')]),
E('div', { 'id': 'content_syslog' }, [
E('div', { 'style': 'padding-bottom: 20px' }, [scrollDownButton]),
E('textarea', {
'id': 'syslog',
'style': 'font-size:12px',
'readonly': 'readonly',
'wrap': 'off',
'rows': loglines.rows
}, [loglines.value]),
E('div', { 'style': 'padding-top: 20px' }, [scrollUpButton])
])
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@@ -0,0 +1,25 @@
'use strict';
'require view';
'require form';
'require tools.widgets as widgets';
return view.extend({
render: function () {
const m = new form.Map('antiblock', _('Routes'));
const s = m.section(form.GridSection, 'route', _('Routes'), _('It is necessary to enter from 1 to 32 values:'));
s.optional = false;
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
let o = s.option(widgets.NetworkSelect, 'gateway', _('Gateway'), _('Gateway'));
o.loopback = true;
o.nocreate = true;
o = s.option(form.Value, 'domains_path', _('Domains path'), _('Domains path/URL. If you want to add domains via LuCI, specify the files in the /etc/antiblock folder.'));
o.default = '/etc/antiblock/';
return m.render();
}
});

View File

@@ -0,0 +1,73 @@
'use strict';
'require view';
'require fs';
'require poll';
'require ui';
'require uci';
let main_config;
return view.extend({
retrieveLog: async function () {
return fs.read_direct('/tmp/antiblock/stat.txt').then(function (logdata) {
const loglines = logdata.trim().split(/\n/).map(function (line) {
return line.replace(/^<\d+>/, '');
});
return { value: loglines.join('\n'), rows: loglines.length + 1 };
}).catch(function (err) {
ui.addNotification(null, E('p', {}, _('Unable to load statistics data:') + ' ' + err.message));
return '';
});
},
pollLog: async function () {
const element = document.getElementById('syslog');
if (element) {
const log = await this.retrieveLog();
element.value = log.value;
element.rows = log.rows;
}
},
load: async function () {
await uci.load('antiblock');
main_config = uci.sections('antiblock', 'main');
if (!main_config[0]?.stat || main_config[0]?.stat === '0') {
return;
}
poll.add(this.pollLog.bind(this), 10);
return await this.retrieveLog();
},
render: function (loglines) {
const main_div = E([]);
main_div.appendChild(E('h2', _('Statistics')));
const routes_div = E('div', { class: 'cbi-section' });
routes_div.appendChild(E('div', { class: 'cbi-section-descr' }, _('Statistics are not enabled.')));
main_div.appendChild(routes_div);
if (!main_config[0]?.stat || main_config[0]?.stat === '0') {
return main_div;
}
return E([], [
E('h2', {}, [_('Statistics')]),
E('div', { 'id': 'content_syslog' }, [
E('textarea', {
'id': 'syslog',
'style': 'font-size:12px',
'readonly': 'readonly',
'wrap': 'off',
'rows': loglines.rows
}, [loglines.value])
])
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@@ -0,0 +1,135 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:8
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:10
#: applications/luci-app-antiblock/root/usr/share/luci/menu.d/luci-app-antiblock.json:3
msgid "AntiBlock"
msgstr ""
#: applications/luci-app-antiblock/root/usr/share/luci/menu.d/luci-app-antiblock.json:15
msgid "Args"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:15
msgid "Blacklist"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:30
msgid "Domain count in file:"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:89
#: applications/luci-app-antiblock/root/usr/share/luci/menu.d/luci-app-antiblock.json:31
msgid "Domains"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/routes.js:20
msgid "Domains path"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/routes.js:20
msgid ""
"Domains path/URL. If you want to add domains via LuCI, specify the files in "
"the /etc/antiblock folder."
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:93
msgid "Domains path:"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:13
msgid "Enabled"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/routes.js:16
msgid "Gateway"
msgstr ""
#: applications/luci-app-antiblock/root/usr/share/rpcd/acl.d/luci-app-antiblock.json:3
msgid "Grant UCI and RPC access to LuCI app AntiBlock"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/routes.js:10
msgid "It is necessary to enter from 1 to 32 values:"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:18
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/log.js:47
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/log.js:73
#: applications/luci-app-antiblock/root/usr/share/luci/menu.d/luci-app-antiblock.json:47
msgid "Log"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/log.js:49
msgid "Log is not enabled."
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:103
msgid "Path to file in \"Domains path\" is not set."
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:15
msgid ""
"Prevent adding IP from these subnets to the routing table, optional parameter"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/routes.js:8
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/routes.js:10
#: applications/luci-app-antiblock/root/usr/share/luci/menu.d/luci-app-antiblock.json:23
msgid "Routes"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/log.js:67
msgctxt "scroll to top (the head) of the log file"
msgid "Scroll to head"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/log.js:59
msgctxt "scroll to bottom (the tail) of the log file"
msgid "Scroll to tail"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:18
msgid "Show operations log, optional parameter"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:21
msgid "Show statistics data, optional parameter"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/args.js:21
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/statistics.js:47
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/statistics.js:57
#: applications/luci-app-antiblock/root/usr/share/luci/menu.d/luci-app-antiblock.json:39
msgid "Statistics"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/statistics.js:49
msgid "Statistics are not enabled."
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:59
msgid "Unable to create domains file"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/log.js:18
msgid "Unable to load log data:"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/statistics.js:18
msgid "Unable to load statistics data:"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:64
msgid "Unable to read domains file"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:22
msgid "Unable to write to domains file"
msgstr ""
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:13
#: applications/luci-app-antiblock/htdocs/luci-static/resources/view/antiblock/domains.js:36
msgid "Write domains"
msgstr ""

View File

@@ -1,85 +0,0 @@
#!/bin/sh
# ubus -v list luci.antiblock
# ubus -S call luci.antiblock read_domains
# ubus -S call luci.antiblock write_domains '{"domains":["test0.com","test1.com","test2.com"]}'
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
get_file_path() {
file_path="$(uci -q get antiblock.config.file)"
}
read_domains() {
get_file_path
json_init
if [ -n "$file_path" ]; then
if [ ! -f "$file_path" ]; then
touch "$file_path"
fi
json_add_array "domains"
file_data=$(cat $file_path)
for domain in $file_data; do
json_add_string "" "$domain"
done
json_close_array
else
json_add_array "empty"
json_close_array
fi
json_dump
json_cleanup
}
write_domains() {
get_file_path
if [ -n "$file_path" ]; then
if [ ! -f "$file_path" ]; then
touch "$file_path"
fi
json_load "$1"
json_get_values values "domains"
>$file_path
for key in $values; do
echo "$key" >>$file_path
done
json_cleanup
/etc/init.d/antiblock restart
fi
}
case "$1" in
list)
json_init
json_add_object "read_domains"
json_close_object
json_add_object "write_domains"
json_add_string 'domains' "domains"
json_close_object
json_dump
json_cleanup
;;
call)
case "$2" in
read_domains)
read_domains
;;
write_domains)
read -r input
write_domains "$input"
;;
*)
return 0
;;
esac
;;
*)
return 0
;;
esac

View File

@@ -11,20 +11,44 @@
] ]
} }
}, },
"admin/services/antiblock/form": { "admin/services/antiblock/args": {
"title": "Args View", "title": "Args",
"order": 1, "order": 1,
"action": { "action": {
"type": "view", "type": "view",
"path": "antiblock/form" "path": "antiblock/args"
}
},
"admin/services/antiblock/routes": {
"title": "Routes",
"order": 2,
"action": {
"type": "view",
"path": "antiblock/routes"
} }
}, },
"admin/services/antiblock/domains": { "admin/services/antiblock/domains": {
"title": "Domains file View", "title": "Domains",
"order": 2, "order": 3,
"action": { "action": {
"type": "view", "type": "view",
"path": "antiblock/domains" "path": "antiblock/domains"
} }
},
"admin/services/antiblock/statistics": {
"title": "Statistics",
"order": 4,
"action": {
"type": "view",
"path": "antiblock/statistics"
}
},
"admin/services/antiblock/log": {
"title": "Log",
"order": 5,
"action": {
"type": "view",
"path": "antiblock/log"
}
} }
} }

View File

@@ -2,10 +2,18 @@
"luci-app-antiblock": { "luci-app-antiblock": {
"description": "Grant UCI and RPC access to LuCI app AntiBlock", "description": "Grant UCI and RPC access to LuCI app AntiBlock",
"read": { "read": {
"ubus": { "file": {
"luci.antiblock": [ "/tmp/antiblock/*": [
"read_domains", "read"
"write_domains" ],
"/etc/antiblock/*": [
"read"
],
"/etc/init.d/antiblock restart": [
"exec"
],
"/bin/mkdir /etc/antiblock": [
"exec"
] ]
}, },
"uci": [ "uci": [
@@ -13,6 +21,11 @@
] ]
}, },
"write": { "write": {
"file": {
"/etc/antiblock/*": [
"write"
]
},
"uci": [ "uci": [
"antiblock" "antiblock"
] ]