v2.0. Multi proxy for user entries. TProxy support.

This commit is contained in:
gSpot
2024-11-03 02:20:45 +03:00
parent 8184a68fe8
commit 06219e9328
42 changed files with 2185 additions and 764 deletions
+3 -1
View File
@@ -4,7 +4,9 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=1.6.0-r1
PKG_NAME:=luci-app-ruantiblock
PKG_VERSION:=2.0.0
PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for ruantiblock
LUCI_DEPENDS:=+ruantiblock
LUCI_PKGARCH:=all
@@ -10,26 +10,26 @@ return view.extend({
currentCrontabLines: [],
toDD: function(n){
toDD(n){
return String(n).replace(/^(\d)$/, "0$1");
},
cronStatusString: function(s) {
cronStatusString(s) {
return s || _('No Sсhedule');
},
stringifyRuabTasks: function(str_array) {
stringifyRuabTasks(str_array) {
let current_tasks = str_array.filter(s => s.match(this.crontabRegexp));
return current_tasks.join('\n');
},
setCronStatus: function(value) {
setCronStatus(value) {
document.getElementById('cron_status').value = this.cronStatusString(value);
document.getElementById("btn_cron_del").style.visibility = (value) ?
'visible' : 'hidden';
},
writeCronFile: function() {
writeCronFile() {
let btn_cron_add = document.getElementById('btn_cron_add');
let btn_cron_del = document.getElementById('btn_cron_del');
let crontab_string = this.currentCrontabLines.join('\n');
@@ -53,17 +53,17 @@ return view.extend({
});
},
delRuabShedules: function() {
delRuabShedules() {
this.currentCrontabLines = this.currentCrontabLines.filter(
s => s.match(this.crontabRegexp) ? false : true);
},
delCronSchedule: function(ev) {
delCronSchedule(ev) {
this.delRuabShedules();
return this.writeCronFile();
},
setCronSchedule: function(ev) {
setCronSchedule(ev) {
let hour_interval = document.getElementById('cron_hour_interval').value;
let day_interval = document.getElementById('cron_day_interval').value;
let hour = document.getElementById('cron_hour').value;
@@ -88,7 +88,7 @@ return view.extend({
return this.writeCronFile();
},
onchangeHourInterval: function(e) {
onchangeHourInterval(e) {
let value = e.target.value;
let bool = (value != '');
let cron_hour = document.getElementById('cron_hour');
@@ -106,7 +106,7 @@ return view.extend({
};
},
load: function() {
load() {
return fs.lines(tools.crontabFile).catch(e => {
ui.addNotification(null, E('p', _('Unable to read the contents')
+ ': %s [ %s ]'.format(
@@ -115,7 +115,7 @@ return view.extend({
});
},
render: function(content) {
render(content) {
this.currentCrontabLines = content;
let current_task = this.stringifyRuabTasks(content);
@@ -17,7 +17,7 @@ document.head.append(E('style', {'type': 'text/css'},
return view.extend({
pollInterval : L.env.pollinterval,
secToTimeString: function(value) {
secToTimeString(value) {
let string = '';
if(/^\d+$/.test(value)) {
value = Number(value);
@@ -42,50 +42,103 @@ return view.extend({
return string;
},
formatNftJson: function(data) {
let output = { 'rules': [] };
if(data.rules.nftables && data.rules.nftables.length > 1) {
for(let i of data.rules.nftables) {
if(!i.rule) continue;
let set, bytes;
i.rule.expr.forEach(e => {
if(e.match && e.match.left && e.match.left.payload) {
set = e.match.right.replace('@', '');
}
else if(e.counter) {
bytes = e.counter.bytes;
};
});
output.rules.push([ set, bytes ]);
};
formatNftJson(data) {
let output = { 'sink': [] };
if(data.sink.nftables && data.sink.nftables.length > 1) {
let rules = [];
function parseDnsmasqData(set) {
let sArray = [];
if(data[set].nftables && data[set].nftables.length > 1) {
data[set].nftables.forEach(e => {
if(e.set && e.set.elem) {
e.set.elem.forEach(i => {
if(i.elem) {
sArray.push([ i.elem.val, i.elem.expires ]);
};
});
for(let i of data.sink.nftables) {
if(i.rule) {
let instance = (i.rule.comment === ' ') ? '-main-' : i.rule.comment;
let proto, bytes;
i.rule.expr.forEach(e => {
if(e.match && e.match.left && e.match.left.meta && e.match.left.meta.key && e.match.left.meta.key == "l4proto") {
proto = e.match.right;
}
else if(e.counter) {
bytes = e.counter.bytes;
};
});
rules.push([ instance, proto, bytes ]);
} else {
continue;
};
return sArray;
};
if(data.dnsmasq) {
output.dnsmasq = parseDnsmasqData('dnsmasq');
if(rules.length > 0) {
output.sink = rules;
};
if(data.dnsmasq_bypass) {
output.dnsmasq_bypass = parseDnsmasqData('dnsmasq_bypass');
};
if(data.sink_local && data.sink_local.nftables && data.sink_local.nftables.length > 1) {
output.sink_local = [];
let rules = [];
for(let i of data.sink_local.nftables) {
if(i.rule) {
let instance = (i.rule.comment === ' ') ? '-main-' : i.rule.comment;
let proto, bytes;
i.rule.expr.forEach(e => {
if(e.match && e.match.left && e.match.left.meta && e.match.left.meta.key && e.match.left.meta.key == "l4proto") {
proto = e.match.right;
}
else if(e.counter) {
bytes = e.counter.bytes;
};
});
rules.push([ instance, proto, bytes ]);
} else {
continue;
};
};
if(rules.length > 0) {
output.sink_local = rules;
};
};
function parseDnsmasqData(set) {
let sArray = [];
if(set.nftables && set.nftables.length > 1) {
set.nftables.forEach(e => {
if(e.set && e.set.elem) {
e.set.elem.forEach(i => {
if(i.elem) {
sArray.push([ i.elem.val, i.elem.expires ]);
};
});
};
});
};
return sArray;
};
if(data.dnsmasq) {
output.dnsmasq = parseDnsmasqData(data.dnsmasq);
};
if(data.dnsmasq_bypass) {
output.dnsmasq_bypass = parseDnsmasqData(data.dnsmasq_bypass);
};
if(data.dnsmasq_user_instances) {
output.dnsmasq_user_instances = [];
if(data.dnsmasq_user_instances && data.dnsmasq_user_instances.length > 1) {
for(let i of data.dnsmasq_user_instances) {
if(i.nftables) {
let name;
i.nftables.forEach(e => {
if(e.set) {
name = e.set.name;
};
});
output.dnsmasq_user_instances.push([ name, parseDnsmasqData(i) ]);
};
};
};
};
return output;
},
makeDnsmasqTable: function(ipDataArray) {
makeDnsmasqTable(ipDataArray, title) {
let lines = `<tr class="tr"><td class="td center">${_('No entries available...')}</td></tr>`;
let ipTable = E('table', { 'id': 'ipTable', 'class': 'table' });
@@ -122,6 +175,7 @@ return view.extend({
};
return E([
E('h3', {}, title),
E('div', { 'class': 'log-entries-count' },
`${_('Entries')}: ${ipDataArray.length}`
),
@@ -129,7 +183,7 @@ return view.extend({
]);
},
pollInfo: function() {
pollInfo() {
return fs.exec_direct(tools.execPath, [ 'html-info' ], 'json').catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s ]'.format(e.message, tools.execPath)
@@ -174,11 +228,20 @@ return view.extend({
let nft_data = this.formatNftJson(data);
if(nft_data.rules.length > 0) {
for(let [set, bytes] of nft_data.rules) {
let elem = document.getElementById('rules.' + set);
if(nft_data.sink.length > 0) {
for(let i of nft_data.sink) {
let elem = document.getElementById('sink.' + i[0] + '.' + (i[1] || 'all'));
if(elem) {
elem.textContent = bytes;
elem.textContent = i[2];
};
};
};
if(nft_data.sink_local && nft_data.sink_local.length > 0) {
for(let i of nft_data.sink_local) {
let elem = document.getElementById('sink_local.' + i[0] + '.' + (i[1] || 'all'));
if(elem) {
elem.textContent = i[2];
};
};
};
@@ -186,15 +249,16 @@ return view.extend({
let rdTableWrapper = document.getElementById('rdTableWrapper');
if(rdTableWrapper) {
rdTableWrapper.innerHTML = '';
rdTableWrapper.append(this.makeDnsmasqTable(nft_data.dnsmasq));
rdTableWrapper.append(this.makeDnsmasqTable(nft_data.dnsmasq, _('Dnsmasq')));
};
let rdbTableWrapper = document.getElementById('rdbTableWrapper');
if(rdbTableWrapper) {
rdbTableWrapper.innerHTML = '';
rdbTableWrapper.append(this.makeDnsmasqTable(nft_data.dnsmasq_bypass));
let rdsTableWrapper = document.getElementById('rdsTableWrapper');
if(rdsTableWrapper) {
rdsTableWrapper.innerHTML = '';
for(let i of nft_data.dnsmasq_user_instances) {
rdsTableWrapper.append(this.makeDnsmasqTable(i[1], _('Dnsmasq') + ' ' + i[0]));
};
};
} else {
if(poll.active()) {
poll.stop();
@@ -203,7 +267,7 @@ return view.extend({
});
},
load: function() {
load() {
return fs.exec_direct(tools.execPath, [ 'html-info' ], 'json').catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s ]'.format(e.message, tools.execPath)
@@ -211,7 +275,7 @@ return view.extend({
})
},
render: function(data) {
render(data) {
if(!data) {
return;
};
@@ -220,11 +284,12 @@ return view.extend({
data = JSON.parse(data);
} catch(e) {};
let update_status = null,
user_entries = null,
rules = null,
dnsmasq = null,
dnsmasqBypass = null;
let update_status = null,
user_entries = null,
sink = null,
sink_local = null,
dnsmasq = null,
dnsmasqUserInstances = null;
if(data) {
if(data.status === 'enabled') {
@@ -285,39 +350,87 @@ return view.extend({
let nft_data = this.formatNftJson(data);
if(nft_data.rules) {
let table_rules = E('table', { 'class': 'table' }, [
if(nft_data.sink) {
let table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th left', 'style': 'min-width:33%' },
_('Match-set')),
_('Instance')),
E('th', { 'class': 'th left' }, _('Protocol')),
E('th', { 'class': 'th left' }, _('Bytes')),
]),
]);
for(let i of nft_data.sink) {
let instance = i[0];
let proto = (i[1] === undefined) ? _('all') : i[1];
let bytes = i[2];
for(let [set, bytes] of nft_data.rules) {
if(!set) {
if(!instance) {
continue;
};
table_rules.append(
table.append(
E('tr', { 'class': 'tr' }, [
E('td',{
'class' : 'td left',
'data-title': _('Match-set'),
}, set + ((set.length >= 1) ? (
' (' + set.replace(/^c/, 'CIDR').replace(/^i/, 'IP').replace(/^d/, 'dnsmasq').replace(/^bi/, 'bypass IP').replace(/^bd/, 'bypass dnsmasq').replace(/^fproxy/, 'full proxy') + ')'
) : '')),
E('td', {
'class' : 'td left',
'id' : 'rules.' + set,
'data-title': _('Instance'),
}, instance),
E('td', {
'class' : 'td left',
'data-title': _('Protocol'),
}, proto),
E('td', {
'class' : 'td left',
'id' : 'sink.' + instance + '.' + (i[1] || 'all'),
'data-title': _('Bytes'),
}, bytes),
])
);
};
rules = E([
E('h3', {}, _('Nftables rules')),
table_rules,
};
sink = E([
E('h3', {}, _('Transit traffic')),
table,
]);
};
if(nft_data.sink_local) {
let table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th left', 'style': 'min-width:33%' },
_('Instance')),
E('th', { 'class': 'th left' }, _('Protocol')),
E('th', { 'class': 'th left' }, _('Bytes')),
]),
]);
for(let i of nft_data.sink_local) {
let instance = i[0];
let proto = (i[1] === undefined) ? _('all') : i[1];
let bytes = i[2];
if(!instance) {
continue;
};
table.append(
E('tr', { 'class': 'tr' }, [
E('td', {
'class' : 'td left',
'data-title': _('Instance'),
}, instance),
E('td', {
'class' : 'td left',
'data-title': _('Protocol'),
}, proto),
E('td', {
'class' : 'td left',
'id' : 'sink_local.' + instance + '.' + (i[1] || 'all'),
'data-title': _('Bytes'),
}, bytes),
])
);
};
sink_local = E([
E('h3', {}, _('Local traffic')),
table,
]);
};
@@ -325,24 +438,28 @@ return view.extend({
let rdTableWrapper = E('div', {
'id' : 'rdTableWrapper',
'style': 'width:100%'
}, this.makeDnsmasqTable(nft_data.dnsmasq));
}, this.makeDnsmasqTable(nft_data.dnsmasq, _('Dnsmasq')));
dnsmasq = E([
E('h3', {}, _('Dnsmasq')),
rdTableWrapper,
]);
};
if(nft_data.dnsmasq_bypass) {
let rdbTableWrapper = E('div', {
'id' : 'rdbTableWrapper',
if(nft_data.dnsmasq_user_instances) {
let rdsTableWrapper = E('div', {
'id' : 'rdsTableWrapper',
'style': 'width:100%'
}, this.makeDnsmasqTable(nft_data.dnsmasq_bypass));
});
dnsmasqBypass = E([
E('h3', {}, _('Dnsmasq bypass')),
rdbTableWrapper,
]);
for(let i of nft_data.dnsmasq_user_instances) {
rdsTableWrapper.append(this.makeDnsmasqTable(i[1], _('Dnsmasq') + ' ' + i[0]));
};
if(nft_data.dnsmasq_user_instances.length > 0) {
dnsmasqUserInstances = E([
rdsTableWrapper,
]);
};
};
poll.add(L.bind(this.pollInfo, this), this.pollInterval);
@@ -360,7 +477,7 @@ return view.extend({
E('div', { 'class': 'cbi-section-node' }, update_status)
),
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' }, rules)
E('div', { 'class': 'cbi-section-node' }, sink)
),
];
@@ -373,16 +490,24 @@ return view.extend({
);
}
if(dnsmasqBypass) {
if(sink_local) {
layout.splice(5, 0,
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' }, dnsmasqBypass)
E('div', { 'class': 'cbi-section-node' }, sink_local)
)
);
};
if(dnsmasqUserInstances) {
layout.splice(6, 0,
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' }, dnsmasqUserInstances)
)
);
};
if(dnsmasq) {
layout.splice(6, 0,
layout.splice(7, 0,
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' }, dnsmasq)
)
@@ -1,6 +1,7 @@
'use strict';
'require baseclass';
'require fs';
'require ui';
'require view.ruantiblock.log-widget as widget';
return baseclass.extend({
@@ -1,4 +1,5 @@
'use strict';
'require baseclass';
'require fs';
'require poll';
'require uci';
@@ -15,7 +16,121 @@ const btn_style_warning = 'btn cbi-button-negative important'
return view.extend({
statusTokenValue: null,
disableButtons: function(bool, btn, elems=[]) {
dialogDestroy: baseclass.extend({
__init__(context) {
this.context = context;
},
currentDnsmasqCfgDir: null,
dnsmasqCfgDirsSelect: null,
cancelButton : E('button', {
'id' : 'btn_cancel',
'class': btn_style_neutral,
'click': ui.hideModal,
}, _('Cancel')),
load() {
return L.resolveDefault(fs.list(tools.dnsmasqCfgDirsRoot), null);
},
render(data) {
let section = uci.get(tools.appName, 'config');
this.currentDnsmasqCfgDir = section.dnsmasq_cfg_dir;
let available_cfg_dirs = [];
let dnsmasq_cfg_dirs_arr = data;
if(dnsmasq_cfg_dirs_arr) {
dnsmasq_cfg_dirs_arr.forEach(e => {
let fname = e.name;
if(fname.startsWith('dnsmasq')) {
available_cfg_dirs.push([ fname, tools.dnsmasqCfgDirsRoot + '/' + fname ]);
};
});
};
this.dnsmasqCfgDirsSelect = E('select', {
'id' : 'dnsmasq_cfg_dirs_list',
'class': "cbi-input-select",
}),
available_cfg_dirs.forEach(e => {
this.dnsmasqCfgDirsSelect.append(
E('option', { 'value': e[1] }, e[0]));
});
this.dnsmasqCfgDirsSelect.value = this.currentDnsmasqCfgDir;
ui.showModal(this.title, [
E('h4', _('The service will be disabled and all blacklist data will be deleted. Continue?')),
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' },
_('Dnsmasq config directory')),
E('div', { 'class': 'cbi-value-field' }, [
this.dnsmasqCfgDirsSelect,
E('div', { 'class': 'cbi-value-description' },
_('Change dnsmasq config directory')),
]),
]),
]),
E('div', { 'class': 'right' }, [
this.cancelButton,
' ',
E('button', {
'id' : 'btn_apply',
'class': btn_style_warning,
'click': ui.createHandlerFn(this, this.handleApply),
}, _('Shutdown')),
]),
], 'cbi-modal');
},
handleApply(ev) {
this.cancelButton.disabled = true;
return this.context.appAction('destroy').then(() => {
if(this.dnsmasqCfgDirsSelect.value !== this.currentDnsmasqCfgDir) {
uci.set(tools.appName, 'config', 'dnsmasq_cfg_dir',
this.dnsmasqCfgDirsSelect.value);
uci.save();
uci.apply();
};
}).finally(() => {
this.cancelButton.disabled = false;
ui.hideModal();
});
},
error(e) {
ui.showModal(this.title, [
E('div', { 'class': 'cbi-section' },
E('p', {}, _('An error occurred')
+ ': %s'.format(e.message))
),
E('div', { 'class': 'right' },
E('button', {
'class': btn_style_neutral,
'click': ui.hideModal,
}, _('Dismiss'))
),
]);
},
show() {
ui.showModal(null,
E('p', { 'class': 'spinning' }, _('Loading'))
);
this.load().then(content => {
ui.hideModal();
return this.render(content);
}).catch(e => {
ui.hideModal();
return this.error(e);
})
},
}),
disableButtons(bool, btn, elems=[]) {
let btn_start = elems[1] || document.getElementById("btn_start");
let btn_destroy = elems[4] || document.getElementById("btn_destroy");
let btn_enable = elems[2] || document.getElementById("btn_enable");
@@ -31,7 +146,7 @@ return view.extend({
};
},
getAppStatus: function() {
getAppStatus() {
return Promise.all([
fs.exec(tools.execPath, [ 'raw-status' ]),
fs.exec(tools.execPath, [ 'vpn-route-status' ]),
@@ -46,7 +161,7 @@ return view.extend({
});
},
setAppStatus: function(status_array, elems=[], force_app_code) {
setAppStatus(status_array, elems=[], force_app_code) {
let section = uci.get(tools.appName, 'config');
if(!status_array || typeof(section) !== 'object') {
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(1);
@@ -59,8 +174,7 @@ return view.extend({
let app_status_code = (force_app_code) ? force_app_code : status_array[0].code;
let vpn_route_status_code = status_array[1].code;
let enabled_flag = status_array[2];
let proxy_local_clients = section.proxy_local_clients;
let proxy_mode = section.proxy_mode;
let dnsmasq_cfg_dir = section.dnsmasq_cfg_dir;
let bllist_preset = section.bllist_preset;
let bllist_module = section.bllist_module;
@@ -122,7 +236,6 @@ return view.extend({
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(
app_status_code,
proxy_mode,
bllist_preset,
bllist_module,
vpn_route_status_code);
@@ -132,7 +245,7 @@ return view.extend({
};
},
serviceAction: function(action, button) {
serviceAction(action, button) {
if(button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
@@ -149,7 +262,7 @@ return view.extend({
});
},
appAction: function(action, button) {
appAction(action, button) {
if(button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
@@ -167,13 +280,12 @@ return view.extend({
return this.getAppStatus().then(
(status_array) => {
this.setAppStatus(status_array);
ui.hideModal();
}
);
});
},
statusPoll: function() {
statusPoll() {
return fs.read(tools.tokenFile).then(v => {
v = tools.normalizeValue(v);
if(v != this.statusTokenValue) {
@@ -187,48 +299,21 @@ return view.extend({
});
},
dialogDestroy: function(ev) {
ev.target.blur();
let cancel_button = E('button', {
'class': btn_style_neutral,
'click': ui.hideModal,
}, _('Cancel'));
let shutdown_btn = E('button', {
'class': btn_style_warning,
}, _('Shutdown'));
shutdown_btn.onclick = ui.createHandlerFn(this, () => {
cancel_button.disabled = true;
return this.appAction('destroy');
});
ui.showModal(_('Shutdown'), [
E('div', { 'class': 'cbi-section' }, [
E('p', _('The service will be disabled and all blacklist data will be deleted. Continue?')),
]),
E('div', { 'class': 'right' }, [
shutdown_btn,
' ',
cancel_button,
])
]);
},
load: function() {
load() {
return this.getAppStatus();
},
render: function(status_array) {
render(status_array) {
if(!status_array) {
return;
};
let section = uci.get(tools.appName, 'config');
let proxy_local_clients = (typeof(section) === 'object') ?
section.proxy_local_clients : null;
this.statusTokenValue = (Array.isArray(status_array)) ?
tools.normalizeValue(status_array[4]) : null;
let dialog_destroy = new this.dialogDestroy(this);
let status_string = E('div', {
'id' : 'status',
'name' : 'status',
@@ -281,7 +366,7 @@ return view.extend({
'name' : 'btn_destroy',
'class': btn_style_negative,
}, _('Shutdown'));
btn_destroy.onclick = L.bind(this.dialogDestroy, this);
btn_destroy.onclick = () => dialog_destroy.show();
layout_append(btn_destroy, _('Shutdown'),
_('Complete service shutdown, as well as deleting nftsets and blacklist data'));
@@ -8,11 +8,11 @@
'require view.ruantiblock.tools as tools';
return view.extend({
parsers : {},
parsers : {},
appStatusCode: null,
appStatusCode : null,
depends : function(elem, key, array, empty=true) {
depends(elem, key, array, empty=true) {
if(empty && array.length === 0) {
elem.depends(key, '_dummy');
} else {
@@ -20,20 +20,82 @@ return view.extend({
};
},
validateIpPort: function(section, value) {
validateIpPort(section, value) {
return (/^$|^([0-9]{1,3}\.){3}[0-9]{1,3}(#[\d]{2,5})?$/.test(value)) ? true : _('Expecting:')
+ ` ${_('One of the following:')}\n - ${_('valid IP address')}\n - ${_('valid address#port')}\n`;
},
validateUrl: function(section, value) {
validateUrl(section, value) {
return (/^$|^https?:\/\/[\w.-]+(:[0-9]{2,5})?[\w\/~.&?+=-]*$/.test(value)) ? true : _('Expecting:')
+ ` ${_('valid URL')}\n`;
},
load: function() {
CBIBlockFileEdit: form.Value.extend({
__name__ : 'CBI.BlockFileEdit',
__init__(map, section, ctx, id, file, title, description, callback) {
this.map = map;
this.section = section;
this.ctx = ctx;
this.id = id,
this.optional = true;
this.rmempty = true;
this.file = file;
this.title = title;
this.description = description;
this.callback = callback;
this.content = '';
},
cfgvalue(section_id, option) {
return this.content;
},
formvalue(section_id) {
let value = this.content;
let textarea = document.getElementById('widget.file_edit.content.' + this.id);
if(textarea) {
value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n';
};
return value;
},
write(section_id, formvalue) {
return fs.write(this.file, formvalue).then(rc => {
ui.addNotification(null, E('p', _('Contents have been saved.')),
'info');
if(this.callback) {
return this.callback(rc);
};
}).catch(e => {
ui.addNotification(null, E('p', _('Unable to save the contents')
+ ': %s'.format(e.message)));
});
},
load() {
return L.resolveDefault(fs.read(this.file), '').then(c => {
this.content = c;
});
},
renderWidget(section_id, option_index, cfgvalue) {
return E('textarea', {
'id' : 'widget.file_edit.content.' + this.id,
'class' : 'cbi-input-textarea',
'style' : 'width:100% !important;resize:vertical !important',
'rows' : 10,
'wrap' : 'off',
'spellcheck': 'false',
}, cfgvalue);
},
}),
load() {
return Promise.all([
L.resolveDefault(fs.exec(tools.execPath, [ 'raw-status' ]), 1),
L.resolveDefault(fs.list(tools.parsersDir), null),
L.resolveDefault(fs.list(tools.dnsmasqCfgDirsRoot), null),
uci.load(tools.appName),
]).catch(e => {
ui.addNotification(null, E('p', _('Unable to read the contents')
@@ -43,14 +105,14 @@ return view.extend({
});
},
render: function(data) {
render(data) {
if(!data) {
return;
};
this.appStatusCode = data[0].code;
let p_dir_arr = data[1];
let curent_module = uci.get(tools.appName, 'config', 'bllist_module');
let curent_preset = uci.get(tools.appName, 'config', 'bllist_preset');
this.appStatusCode = data[0].code;
let p_dir_arr = data[1];
let curent_module = uci.get(tools.appName, 'config', 'bllist_module');
let curent_preset = uci.get(tools.appName, 'config', 'bllist_preset');
if(p_dir_arr) {
p_dir_arr.forEach(e => {
@@ -129,7 +191,7 @@ return view.extend({
'<br /><code>#comment<br />domain.net<br />anotherdomain.com</code>'
);
let m, s, o;
let m, s, o, ss;
m = new form.Map(tools.appName, _('Ruantiblock') + ' - ' + _('Settings'));
@@ -140,39 +202,32 @@ return view.extend({
/* Main settings tab */
s.tab('main_settings', _('Main settings'));
// PROXY_MODE
o = s.taboption('main_settings', form.ListValue, 'proxy_mode',
_('Proxy mode'));
o.value('1', 'Tor');
o.value('2', 'VPN');
o.value('3', _('Transparent proxy'));
// PROXY_LOCAL_CLIENTS
let proxy_local_clients = s.taboption('main_settings', form.Flag, 'proxy_local_clients',
_('Apply proxy rules to router application traffic'));
proxy_local_clients.rmempty = false;
s.tab('main_tab', _('Main settings'));
// ENABLE_LOGGING
o = s.taboption('main_settings', form.Flag, 'enable_logging',
o = s.taboption('main_tab', form.Flag, 'enable_logging',
_('Logging events'));
o.rmempty = false;
// update_at_startup
o = s.taboption('main_settings', form.Flag, 'update_at_startup',
o = s.taboption('main_tab', form.Flag, 'update_at_startup',
_('Update at startup'));
o.description = _('Update blacklist after system startup');
o.rmempty = false;
// PROXY_LOCAL_CLIENTS
o = s.taboption('main_tab', form.Flag, 'proxy_local_clients',
_('Apply proxy rules to router application traffic'));
o.rmempty = false;
// NFTSET_CLEAR_SETS
o = s.taboption('main_settings', form.Flag, 'nftset_clear_sets',
o = s.taboption('main_tab', form.Flag, 'nftset_clear_sets',
_('Clean up nftsets before updating blacklist'));
o.description = _('Reduces RAM consumption during update');
o.rmempty = false;
// ALLOWED_HOSTS_MODE
o = s.taboption('main_settings', form.ListValue, 'allowed_hosts_mode',
o = s.taboption('main_tab', form.ListValue, 'allowed_hosts_mode',
_('Host filter'));
o.value('0', _('Disabled'));
o.value('1', _('Only listed hosts'));
@@ -180,40 +235,29 @@ return view.extend({
o.description = _('Restriction of hosts that are allowed to bypass blocking');
// ALLOWED_HOSTS_LIST
o = s.taboption('main_settings', form.DynamicList, 'allowed_hosts_list',
o = s.taboption('main_tab', form.DynamicList, 'allowed_hosts_list',
_('IP addresses for host filter'));
o.datatype = 'ip4addr';
// ENABLE_FPROXY
o = s.taboption('main_settings', form.Flag, 'enable_fproxy',
_('Enable full proxy mode'));
o.description = _('All traffic of the specified hosts passes through the proxy, without a blacklist');
o.rmempty = false;
// FPROXY_LIST
o = s.taboption('main_settings', form.DynamicList, 'fproxy_list',
_('IP addresses for full proxy mode'));
o.datatype = 'ip4addr';
/* Tor tab */
s.tab('tor_settings', _('Tor mode'));
s.tab('tor_tab', _('Tor mode'));
// TOR_TRANS_PORT
o = s.taboption('tor_settings', form.Value, 'tor_trans_port',
o = s.taboption('tor_tab', form.Value, 'tor_trans_port',
_('Transparent proxy port'));
o.rmempty = false;
o.datatype = 'port';
// ONION_DNS_ADDR
o = s.taboption('tor_settings', form.Value, 'onion_dns_addr',
o = s.taboption('tor_tab', form.Value, 'onion_dns_addr',
_("Optional DNS resolver for '.onion' zone"), '<code>ipaddress#port</code>');
o.rmempty = false;
o.validate = this.validateIpPort;
// Torrc edit dialog
o = s.taboption('tor_settings', form.Button, '_torrc_btn',
o = s.taboption('tor_tab', form.Button, '_torrc_btn',
_('Tor configuration file'));
o.onclick = () => torrc_edit.show();
o.inputtitle = _('Edit');
@@ -222,10 +266,10 @@ return view.extend({
/* VPN tab */
s.tab('vpn_settings', _('VPN mode'));
s.tab('vpn_tab', _('VPN mode'));
// IF_VPN
o = s.taboption('vpn_settings', widgets.DeviceSelect, 'if_vpn',
o = s.taboption('vpn_tab', widgets.DeviceSelect, 'if_vpn',
_('VPN interface'));
o.multiple = false;
o.noaliases = true;
@@ -233,13 +277,13 @@ return view.extend({
o.default = 'tun0';
// VPN_GW_IP
o = s.taboption('vpn_settings', form.Value, 'vpn_gw_ip',
o = s.taboption('vpn_tab', form.Value, 'vpn_gw_ip',
_('VPN gateway IP address'),
_('If not specified, the VPN interface address is used (or peer address for PPP protocols)'));
o.datatype = 'ip4addr(1)';
// VPN_ROUTE_CHECK
o = s.taboption('vpn_settings', form.ListValue, 'vpn_route_check',
o = s.taboption('vpn_tab', form.ListValue, 'vpn_route_check',
_('Type of adding a VPN rule to the routing table'));
o.value('0', 'hotplug.d');
o.value('1', 'ruab_route_check');
@@ -248,32 +292,46 @@ return view.extend({
_('ruab_route_check - script that regularly checks an entry in the routing table.');
/* Proxy tab */
/* Tproxy tab */
s.tab('proxy_settings', _('Transparent proxy mode'));
s.tab('tproxy_tab', _('Transparent proxy mode'));
// T_PROXY_TYPE
o = s.taboption('tproxy_tab', form.ListValue, 't_proxy_type',
_('Proxy type'));
o.value('0', _('redirect'));
o.value('1', _('tproxy'));
o.description = _('Statement in nftables rules');
// T_PROXY_PORT_TCP
o = s.taboption('proxy_settings', form.Value, 't_proxy_port_tcp',
o = s.taboption('tproxy_tab', form.Value, 't_proxy_port_tcp',
_('Transparent proxy TCP port'));
o.rmempty = false;
o.datatype = 'port';
// T_PROXY_ALLOW_UDP
o = s.taboption('proxy_settings', form.Flag, 't_proxy_allow_udp',
o = s.taboption('tproxy_tab', form.Flag, 't_proxy_allow_udp',
_('Send UDP traffic to transparent proxy'));
o.rmempty = false;
// T_PROXY_PORT_UDP
o = s.taboption('proxy_settings', form.Value, 't_proxy_port_udp',
o = s.taboption('tproxy_tab', form.Value, 't_proxy_port_udp',
_('Transparent proxy UDP port'));
o.rmempty = false;
o.datatype = 'port';
/* Blacklist module tab */
/* Blacklist tab */
s.tab('blacklist_tab', _('Blacklist settings'));
// PROXY_MODE
o = s.taboption('blacklist_tab', form.ListValue, 'proxy_mode',
_('Proxy mode'));
o.value('1', 'Tor');
o.value('2', 'VPN');
o.value('3', _('Transparent proxy'));
// BLLIST_PRESET
let bllist_preset = s.taboption('blacklist_tab', form.ListValue,
'bllist_preset', _('Blacklist update mode'));
@@ -312,29 +370,16 @@ return view.extend({
o.rmempty = false;
o.default = 0;
// ADD_USER_ENTRIES
o = s.taboption('blacklist_tab', form.Flag, 'add_user_entries',
_('Enable user entries'), _('Add user entries to the blacklist when updating'));
// ENABLE_FPROXY
o = s.taboption('blacklist_tab', form.Flag, 'enable_fproxy',
_('Enable full proxy mode'));
o.description = _('All traffic of the specified hosts passes through the proxy, without a blacklist');
o.rmempty = false;
o.default = 0;
o.depends({ bllist_preset: '', '!reverse': true });
// USER_ENTRIES edit dialog
o = s.taboption('blacklist_tab', form.Button, '_user_entries_btn',
_('User entries'));
o.onclick = () => user_entries_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
// USER_ENTRIES_REMOTE
o = s.taboption('blacklist_tab', form.DynamicList, 'user_entries_remote',
_('URLs of remote user entries file'));
o.validate = this.validateUrl;
// USER_ENTRIES_DNS
o = s.taboption('blacklist_tab', form.Value, 'user_entries_dns',
_("DNS server that is used for the user's FQDN entries"), '<code>ipaddress[#port]</code>');
o.validate = this.validateIpPort;
// FPROXY_LIST
o = s.taboption('blacklist_tab', form.DynamicList, 'fproxy_list',
_('IP addresses for full proxy mode'));
o.datatype = 'ip4addr';
// BYPASS_MODE
o = s.taboption('blacklist_tab', form.Flag, 'bypass_mode',
@@ -354,6 +399,7 @@ return view.extend({
_('DNS server that is used for the FQDN entries of exclusion list'), '<code>ipaddress[#port]</code>');
o.validate = this.validateIpPort;
if(availableParsers) {
bllist_preset.description += '<br /> ( * - ' + _('requires installed blacklist module') + ' )';
@@ -398,10 +444,9 @@ return view.extend({
// BLLIST_GR_EXCLUDED_SLD_FILE edit dialog
o = s.taboption('parser_settings_tab', form.Button, '_gr_excluded_sld_btn',
_('2nd level domains that are excluded from optimization'));
o.onclick = () => gr_excluded_sld_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
//o.description = _('e.g:') + ' <code>livejournal.com</code>';
o.onclick = () => gr_excluded_sld_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
// BLLIST_ENABLE_IDN
o = s.taboption('parser_settings_tab', form.Flag, 'bllist_enable_idn',
@@ -447,10 +492,9 @@ return view.extend({
// BLLIST_GR_EXCLUDED_NETS_FILE edit dialog
o = s.taboption('parser_settings_tab', form.Button, '_gr_excluded_nets_btn',
_('IP subnet patterns (/24) that are excluded from optimization'));
o.onclick = () => gr_excluded_nets_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
//o.description = _('e.g:') + ' <code>192.168.1.</code>';
o.onclick = () => gr_excluded_nets_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
// BLLIST_SUMMARIZE_IP
o = s.taboption('parser_settings_tab', form.Flag, 'bllist_summarize_ip',
@@ -461,7 +505,180 @@ return view.extend({
o = s.taboption('parser_settings_tab', form.Flag, 'bllist_summarize_cidr',
_("Summarize '/24' networks"));
o.rmempty = false;
};
/* User entries tab */
s.tab('user_entries_tab', _('User entries'));
o = s.taboption('user_entries_tab', form.SectionValue, 'user_instance', form.GridSection,
'user_instance');
ss = o.subsection;
ss.addremove = false;
ss.sortable = false;
ss.nodescriptions = true;
ss.modaltitle = `${_('User entries')} - %s`;
ss.max_cols = 2;
/* User entries main settings tab */
ss.tab('u_main_tab', _('Main settings'));
// U_ENABLED
o = ss.taboption('u_main_tab', form.Flag, 'u_enabled',
_('Enabled'),
);
o.rmempty = false;
o.default = '1';
o.editable = true;
o.modalonly = false;
// description
o = ss.taboption('u_main_tab', form.Value, 'u_description',
_("Description"));
o.datatype = 'maxlength(100)';
o.modalonly = null;
// U_PROXY_MODE
o = ss.taboption('u_main_tab', form.ListValue, 'u_proxy_mode',
_('Proxy mode'));
o.value('1', 'Tor');
o.value('2', 'VPN');
o.value('3', _('Transparent proxy'));
o.default = '2';
o.modalonly = true;
// U_SKIP_MARKED_PACKETS
o = ss.taboption('u_main_tab', form.Flag, 'u_skip_marked_packets',
_('Lowest priority'));
o.description = _('This proxy will receive traffic last, even after the main blacklist');
o.rmempty = false;
o.modalonly = true;
// U_ENABLE_FPROXY
o = ss.taboption('u_main_tab', form.Flag, 'u_enable_fproxy',
_('Enable full proxy mode'));
o.description = _('All traffic of the specified hosts passes through the proxy, without a blacklist');
o.rmempty = false;
o.modalonly = true;
// U_FPROXY_LIST
o = ss.taboption('u_main_tab', form.DynamicList, 'u_fproxy_list',
_('IP addresses for full proxy mode'));
o.datatype = 'ip4addr';
o.modalonly = true;
/* User entries tor tab */
ss.tab('u_tor_tab', _('Tor mode'));
// U_TOR_TRANS_PORT
o = ss.taboption('u_tor_tab', form.Value, 'u_tor_trans_port',
_('Transparent proxy port'));
o.rmempty = false;
o.datatype = 'port';
o.modalonly = true;
// U_ONION_DNS_ADDR
o = ss.taboption('u_tor_tab', form.Value, 'u_onion_dns_addr',
_("Optional DNS resolver for '.onion' zone"), '<code>ipaddress#port</code>');
o.rmempty = false;
o.validate = this.validateIpPort;
o.modalonly = true;
/* User entries VPN tab */
ss.tab('u_vpn_tab', _('VPN mode'));
// U_IF_VPN
o = ss.taboption('u_vpn_tab', widgets.DeviceSelect, 'u_if_vpn',
_('VPN interface'));
o.multiple = false;
o.noaliases = true;
o.rmempty = false;
o.default = 'tun0';
o.modalonly = true;
// U_VPN_GW_IP
o = ss.taboption('u_vpn_tab', form.Value, 'u_vpn_gw_ip',
_('VPN gateway IP address'),
_('If not specified, the VPN interface address is used (or peer address for PPP protocols)'));
o.datatype = 'ip4addr(1)';
o.modalonly = true;
/* User entries tproxy tab */
ss.tab('u_tproxy_tab', _('Transparent proxy mode'));
// U_T_PROXY_TYPE
o = ss.taboption('u_tproxy_tab', form.ListValue, 'u_t_proxy_type',
_('Proxy type'));
o.value('0', _('redirect'));
o.value('1', _('tproxy'));
o.description = _('Statement in nftables rules');
// U_T_PROXY_PORT_TCP
o = ss.taboption('u_tproxy_tab', form.Value, 'u_t_proxy_port_tcp',
_('Transparent proxy TCP port'));
o.rmempty = false;
o.datatype = 'port';
o.modalonly = true;
// U_T_PROXY_ALLOW_UDP
o = ss.taboption('u_tproxy_tab', form.Flag, 'u_t_proxy_allow_udp',
_('Send UDP traffic to transparent proxy'));
o.rmempty = false;
o.modalonly = true;
// U_T_PROXY_PORT_UDP
o = ss.taboption('u_tproxy_tab', form.Value, 'u_t_proxy_port_udp',
_('Transparent proxy UDP port'));
o.rmempty = false;
o.datatype = 'port';
o.modalonly = true;
/* User entries items tab */
ss.tab('u_entries_tab', _('Entries'));
ss.addModalOptions = (s, section_id, ev) => {
// user entries edit dialog
o = s.taboption('u_entries_tab', this.CBIBlockFileEdit, this,
'user-entries',
tools.userListsDir + '/' + s.section,
_('Edit entries'),
_('One entry (IP, CIDR or FQDN) per line. In the FQDN records, you can specify the DNS server for resolving this domain (separated by a space). You can also comment on lines (<code>#</code> is the first character of a line).<br />Examples:') +
'<br /><code>#comment<br />domain.net<br />sub.domain.com 8.8.8.8<br />sub.domain.com 8.8.8.8#53<br />74.125.131.19<br />74.125.0.0/16</code>'
);
// DEBUG
console.log(tools.userListsDir + '/' + s.section);
o.modalonly = true;
// U_ENTRIES_REMOTE
o = s.taboption('u_entries_tab', form.DynamicList, 'u_entries_remote',
_('URLs of remote user entries file'));
o.validate = this.validateUrl;
o.modalonly = true;
// U_ENABLE_ENTRIES_REMOTE_PROXY
o = s.taboption('u_entries_tab', form.Flag, 'u_enable_entries_remote_proxy',
_('Downloading files via proxy'), _('Turn on if files are blocked'));
o.rmempty = false;
o.default = 0;
// U_ENTRIES_DNS
o = s.taboption('u_entries_tab', form.Value, 'u_entries_dns',
_("DNS server that is used for the user's FQDN entries"), '<code>ipaddress[#port]</code>');
o.validate = this.validateIpPort;
o.modalonly = true;
};
let map_promise = m.render();
@@ -469,12 +686,28 @@ return view.extend({
return map_promise;
},
handleSaveApply: function(ev, mode) {
return this.handleSave(ev).then(() => {
handleSave(ev, restart) {
let tasks = [];
document.getElementById('maincontent')
.querySelectorAll('.cbi-map').forEach((map, i, a) => {
let res = DOM.callClassMethod(map, 'save');
if(restart && i == a.length - 1 && this.appStatusCode != 1 && this.appStatusCode != 2) {
res.then(() => {
window.setTimeout(() => {
fs.exec_direct(tools.execPath, [ 'restart' ]).then(
() => console.log(tools.execPath + ' restarted...')
);
}, 1000);
});
};
tasks.push(res);
});
return Promise.all(tasks);
},
handleSaveApply(ev, mode) {
return this.handleSave(ev, true).then(() => {
ui.changes.apply(mode == '0');
if(this.appStatusCode != 1 && this.appStatusCode != 2) {
window.setTimeout(() => fs.exec(tools.execPath, [ 'restart' ]), 3000);
};
});
},
});
@@ -38,8 +38,10 @@ return baseclass.extend({
execPath : '/usr/bin/ruantiblock',
tokenFile : '/var/run/ruantiblock.token',
parsersDir : '/usr/libexec/ruantiblock',
dnsmasqCfgDirsRoot: '/tmp',
torrcFile : '/etc/tor/torrc',
userEntriesFile : '/etc/ruantiblock/user_entries',
userListsDir : '/etc/ruantiblock/user_lists',
bypassEntriesFile : '/etc/ruantiblock/bypass_entries',
fqdnFilterFile : '/etc/ruantiblock/fqdn_filter',
ipFilterFile : '/etc/ruantiblock/ip_filter',
@@ -76,7 +78,7 @@ return baseclass.extend({
expect: { result: false }
}),
getInitStatus: function(name) {
getInitStatus(name) {
return this.callInitStatus(name).then(res => {
if(res) {
return res[name].enabled;
@@ -89,7 +91,7 @@ return baseclass.extend({
});
},
handleServiceAction: function(name, action) {
handleServiceAction(name, action) {
return this.callInitAction(name, action).then(success => {
if(!success) {
throw _('Command failed');
@@ -101,13 +103,12 @@ return baseclass.extend({
});
},
normalizeValue: function(v) {
normalizeValue(v) {
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
},
makeStatusString: function(
app_status_code,
proxy_mode,
bllist_preset,
bllist_module,
vpn_route_status_code) {
@@ -152,14 +153,6 @@ return baseclass.extend({
%s %s
</td>
</tr>
<tr class="tr">
<td class="td left">
${_('Proxy mode')}:
</td>
<td class="td left">
%s
</td>
</tr>
<tr class="tr">
<td class="td left">
${_('Blacklist update mode')}:
@@ -172,10 +165,9 @@ return baseclass.extend({
`.format(
spinning,
app_status_label,
(app_status_code != 2 && proxy_mode == 2 && vpn_route_status_code != 0)
(app_status_code != 2 && vpn_route_status_code != 0)
? '<span class="label-status error">'
+ _('VPN routing error! Need restart') + '</span>' : '',
(proxy_mode == 3) ? _('Transparent proxy') : (proxy_mode == 2) ? 'VPN' : 'Tor',
(!bllist_preset || bllist_preset === '') ? _('user entries only') :
(this.blacklistPresets[bllist_preset]) ?
`<span style="cursor:help; border-bottom:1px dotted" data-tooltip="${this.blacklistPresets[bllist_preset][2]}">
@@ -185,7 +177,7 @@ return baseclass.extend({
},
fileEditDialog: baseclass.extend({
__init__: function(file, title, description, callback, file_exists=false) {
__init__(file, title, description, callback, file_exists=false) {
this.file = file;
this.title = title;
this.description = description;
@@ -193,11 +185,11 @@ return baseclass.extend({
this.file_exists = file_exists;
},
load: function() {
load() {
return L.resolveDefault(fs.read(this.file), '');
},
render: function(content) {
render(content) {
ui.showModal(this.title, [
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'cbi-section-descr' }, this.description),
@@ -230,7 +222,7 @@ return baseclass.extend({
]);
},
handleSave: function(ev) {
handleSave(ev) {
let textarea = document.getElementById('widget.modal_content');
let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n';
@@ -249,7 +241,7 @@ return baseclass.extend({
});
},
error: function(e) {
error(e) {
if(!this.file_exists && e instanceof Error && e.name === 'NotFoundError') {
return this.render();
} else {
@@ -268,7 +260,7 @@ return baseclass.extend({
};
},
show: function() {
show() {
ui.showModal(null,
E('p', { 'class': 'spinning' }, _('Loading'))
);
+50 -5
View File
@@ -70,6 +70,9 @@ msgstr "Байты"
msgid "Cancel"
msgstr "Отмена"
msgid "Change dnsmasq config directory"
msgstr "Изменить директорию конфигов dnsmasq"
msgid "Changes have been saved."
msgstr "Изменения сохранены."
@@ -110,12 +113,18 @@ msgstr "День"
msgid "Debug"
msgstr "Отладка"
msgid "Description"
msgstr "Описание"
msgid "Disabled"
msgstr "Отключено"
msgid "Dismiss"
msgstr "Закрыть"
msgid "Dnsmasq config directory"
msgstr "Директория конфигов dnsmasq"
msgid "Download error"
msgstr "Ошибка загрузки"
@@ -125,9 +134,15 @@ msgstr "Скачать журнал"
msgid "Downloading a blacklist via proxy"
msgstr "Скачивать блэклист через прокси"
msgid "Downloading files via proxy"
msgstr "Скачивать файлы через прокси"
msgid "Edit"
msgstr "Изменить"
msgid "Edit entries"
msgstr "Изменить записи"
msgid "Emergency"
msgstr "Чрезвычайная ситуация"
@@ -233,15 +248,15 @@ msgstr "Шаблоны IP подсетей (/24) не подлежащих оп
msgid "Info"
msgstr "Информация"
msgid "Instance"
msgstr "Экземпляр"
msgid "Interval"
msgstr "Интервал"
msgid "Invalid regular expression"
msgstr "Неправильное регулярное выражение"
msgid "Nftables rules"
msgstr "Правила nftables"
msgid "Last blacklist update"
msgstr "Последнее обновление блэклиста"
@@ -257,6 +272,9 @@ msgstr "Список хостов, которые исключаются из о
msgid "Loading"
msgstr "Загрузка"
msgid "Local traffic"
msgstr "Локальный трафик"
msgid "Log"
msgstr "Лог"
@@ -269,6 +287,9 @@ msgstr "Уровни логирования"
msgid "Logread not found"
msgstr "Logread не найден"
msgid "Lowest priority"
msgstr "Самый низкий приоритет"
msgid "Main settings"
msgstr "Основные настройки"
@@ -373,9 +394,15 @@ msgstr "Отбор IP адресов из блэклиста по шаблона
msgid "Pick domains from blacklist by FQDN filter patterns"
msgstr "Отбор доменов из блэклиста по шаблонам фильтра FQDN"
msgid "Protocol"
msgstr "Протокол"
msgid "Proxy mode"
msgstr "Режим прокси"
msgid "Proxy type"
msgstr "Тип прокси"
msgid "Reduces RAM consumption during update"
msgstr "Уменьшает потребление оперативной памяти при обновлении"
@@ -418,8 +445,11 @@ msgstr "Настройки"
msgid "Shutdown"
msgstr "Выключение"
msgid "Size in memory"
msgstr "Размер в памяти"
msgid "Skip marked packets"
msgstr "Пропускать помеченные пакеты"
msgid "Skip packets that have already been marked in previous rules"
msgstr "Пропускать пакеты, которые уже были помечены в предыдущих правилах"
msgid "Sorting entries"
msgstr "Сортировка записей"
@@ -430,6 +460,9 @@ msgstr "Запускается"
msgid "Statistics"
msgstr "Статистика"
msgid "Statement in nftables rules"
msgstr "Действие в правилах nftables"
msgid "Status"
msgstr "Статус"
@@ -469,6 +502,9 @@ msgid ""
msgstr ""
"Служба будет выключена и все данные блэклиста будут удалены. Продолжить?"
msgid "This proxy will receive traffic last, even after the main blacklist"
msgstr "В этот прокси трафик будет попадать в последнюю очередь, даже после основного блэклиста"
msgid "Time"
msgstr "Время"
@@ -487,6 +523,9 @@ msgstr "Конфигурационный файл Tor"
msgid "Tor mode"
msgstr "Режим Tor"
msgid "Transit traffic"
msgstr "Транзитный трафик"
msgid "Transparent proxy"
msgstr "Прозрачный прокси"
@@ -505,6 +544,9 @@ msgstr "UDP порт прозрачного прокси"
msgid "Turn on if blacklist source is blocked"
msgstr "Включите, если источник блэклиста заблокирован"
msgid "Turn on if files are blocked"
msgstr "Включите, если файлы заблокированы"
msgid "Type a search pattern..."
msgstr "Введите шаблон для поиска"
@@ -568,6 +610,9 @@ msgstr "Ошибка маршрутизации VPN! Необходим пере
msgid "Warning"
msgstr "Внимание"
msgid "all"
msgstr "все"
msgid "ascending"
msgstr "по возрастанию"
@@ -55,6 +55,9 @@ msgstr ""
msgid "Cancel"
msgstr ""
msgid "Change dnsmasq config directory"
msgstr ""
msgid "Changes have been saved."
msgstr ""
@@ -95,12 +98,18 @@ msgstr ""
msgid "Debug"
msgstr ""
msgid "Description"
msgstr ""
msgid "Disabled"
msgstr ""
msgid "Dismiss"
msgstr ""
msgid "Dnsmasq config directory"
msgstr ""
msgid "Download error"
msgstr ""
@@ -110,9 +119,15 @@ msgstr ""
msgid "Downloading a blacklist via proxy"
msgstr ""
msgid "Downloading files via proxy"
msgstr ""
msgid "Edit"
msgstr ""
msgid "Edit entries"
msgstr ""
msgid "Emergency"
msgstr ""
msgid "Enable"
@@ -214,15 +229,15 @@ msgstr ""
msgid "Info"
msgstr ""
msgid "Instance"
msgstr ""
msgid "Interval"
msgstr ""
msgid "Invalid regular expression"
msgstr ""
msgid "Nftables rules"
msgstr ""
msgid "Last blacklist update"
msgstr ""
@@ -238,6 +253,9 @@ msgstr ""
msgid "Loading"
msgstr ""
msgid "Local traffic"
msgstr ""
msgid "Log"
msgstr ""
@@ -250,6 +268,9 @@ msgstr ""
msgid "Logread not found"
msgstr ""
msgid "Lowest priority"
msgstr ""
msgid "Main settings"
msgstr ""
@@ -342,9 +363,15 @@ msgstr ""
msgid "Pick domains from blacklist by FQDN filter patterns"
msgstr ""
msgid "Protocol"
msgstr ""
msgid "Proxy mode"
msgstr ""
msgid "Proxy type"
msgstr ""
msgid "Reduces RAM consumption during update"
msgstr ""
@@ -386,9 +413,6 @@ msgstr ""
msgid "Shutdown"
msgstr ""
msgid "Size in memory"
msgstr ""
msgid "Sorting entries"
msgstr ""
@@ -397,6 +421,10 @@ msgstr ""
msgid "Statistics"
msgstr ""
msgid "Statement in nftables rules"
msgstr ""
msgid "Status"
msgstr ""
@@ -429,6 +457,9 @@ msgid ""
"Continue?"
msgstr ""
msgid "This proxy will receive traffic last, even after the main blacklist"
msgstr ""
msgid "Time"
msgstr ""
@@ -447,6 +478,9 @@ msgstr ""
msgid "Tor mode"
msgstr ""
msgid "Transit traffic"
msgstr ""
msgid "Transparent proxy"
msgstr ""
@@ -465,6 +499,9 @@ msgstr ""
msgid "Turn on if blacklist source is blocked"
msgstr ""
msgid "Turn on if files are blocked"
msgstr ""
msgid "Type a search pattern..."
msgstr ""
@@ -509,6 +546,7 @@ msgid "URLs of remote user entries file"
msgid "Use optional DNS resolver"
msgstr ""
msgid "User entries"
msgstr ""
@@ -527,6 +565,9 @@ msgstr ""
msgid "Warning"
msgstr ""
msgid "all"
msgstr "все"
msgid "ascending"
msgstr ""
@@ -7,7 +7,7 @@
"/usr/libexec/ruantiblock": [ "list" ],
"/etc/ruantiblock/fqdn_filter": [ "read" ],
"/etc/ruantiblock/ip_filter": [ "read" ],
"/etc/ruantiblock/user_entries": [ "read" ],
"/etc/ruantiblock/user_lists/*": [ "read" ],
"/etc/ruantiblock/bypass_entries": [ "read" ],
"/etc/ruantiblock/gr_excluded_nets": [ "read" ],
"/etc/ruantiblock/gr_excluded_sld": [ "read" ],
@@ -16,7 +16,8 @@
"/etc/crontabs/root": [ "read" ],
"/usr/bin/ruantiblock*": [ "exec" ],
"/sbin/logread -e ruantiblock:": [ "exec" ],
"/usr/sbin/logread -e ruantiblock:": [ "exec" ]
"/usr/sbin/logread -e ruantiblock:": [ "exec" ],
"/tmp": [ "list" ]
},
"uci": [ "network", "ruantiblock" ],
"ubus": {
@@ -27,7 +28,7 @@
"file": {
"/etc/ruantiblock/fqdn_filter": [ "write" ],
"/etc/ruantiblock/ip_filter": [ "write" ],
"/etc/ruantiblock/user_entries": [ "write" ],
"/etc/ruantiblock/user_lists/*": [ "write" ],
"/etc/ruantiblock/bypass_entries": [ "write" ],
"/etc/ruantiblock/gr_excluded_nets": [ "write" ],
"/etc/ruantiblock/gr_excluded_sld": [ "write" ],