Initial commit

This commit is contained in:
gSpot
2020-06-19 20:43:08 +03:00
parent 4cc2914f10
commit cf2a9062b6
34 changed files with 5644 additions and 0 deletions
@@ -0,0 +1,220 @@
'use strict';
'require fs';
'require ui';
'require view.ruantiblock.tools as tools';
let crontab_regexp = new RegExp(`^(\\*?\\/?(\\d){0,2}\\s){5}${tools.exec_path} update(\n)?`, 'gm');
let current_crontab_content;
function to_dd(n){
return String(n).replace(/^(\d)$/, "0$1");
};
function cron_status_string(s) {
return s || _('No Shedule');
}
function pick_cron_task(content) {
if(!content){
return;
};
let current_tasks = content.match(crontab_regexp) || [];
return current_tasks.join('');
};
function set_cron_status(value) {
document.getElementById('cron_status').value = cron_status_string(value);
document.getElementById("btn_cron_del").style.visibility = (value) ? 'visible' : 'hidden';
}
function write_cron_file() {
let btn_cron_add = document.getElementById('btn_cron_add');
let btn_cron_del = document.getElementById('btn_cron_del');
if(!current_crontab_content) {
ui.addNotification(null, E('p', _('No changes to save.')));
btn_cron_add.disabled = false;
return;
};
return fs.write(tools.crontab_file, current_crontab_content).then(rc => {
ui.addNotification(null, E('p',_('Changes have been saved.')), 'info');
set_cron_status(pick_cron_task(current_crontab_content));
}).then(() => {
return fs.exec('/etc/init.d/cron', [ 'enabled' ]).then(res => {
if(res.code !== 0) {
return fs.exec('/etc/init.d/cron', [ 'enable' ]);
};
}).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s<br />[ %s ]'.format(e.message, '/etc/init.d/cron')));
});
}).finally(() => {
return fs.exec('/etc/init.d/cron', [ 'restart' ]).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s<br />[ %s ]'.format(e.message, '/etc/init.d/cron')));
});
}).catch(e => {
ui.addNotification(null, E('p', _('Unable to save the changes')
+ ': %s<br />[ %s ]'.format(
e.message, tools.crontab_file
)));
});
}
function del_cron_schedule(ev) {
if(current_crontab_content) {
current_crontab_content = current_crontab_content.replace(crontab_regexp, "");
};
return write_cron_file();
};
function set_cron_schedule(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;
let min = document.getElementById('cron_min').value;
let task_string = '%s %s %s * * %s update\n'.format(
min,
(!hour_interval) ? hour : (hour_interval == "1") ? '*' : '*/' + hour_interval,
(hour_interval || day_interval == "1") ? '*' : '*/' + day_interval,
tools.exec_path
);
if(current_crontab_content) {
current_crontab_content = current_crontab_content.replace(crontab_regexp, "") + task_string;
};
return write_cron_file();
};
function onchange_hour_interval(e) {
let value = e.target.value;
let bool = (value != '');
let cron_hour = document.getElementById('cron_hour');
let cron_day_interval = document.getElementById('cron_day_interval');
cron_hour.disabled = bool;
cron_day_interval.disabled = bool;
// For luci-theme-material
if(bool) {
cron_hour.style.opacity = '50%';
cron_day_interval.style.opacity = '50%';
} else {
cron_hour.style.opacity = '100%';
cron_day_interval.style.opacity = '100%';
};
}
return L.view.extend({
load: function() {
return fs.read(tools.crontab_file).catch(e => {
ui.addNotification(null, E('p', _('Unable to read the contents')
+ ': %s<br />[ %s ]'.format(
e.message, tools.crontab_file
)));
});
},
render: function(content) {
current_crontab_content = content;
let current_task = pick_cron_task(content);
let cron_status = E('textarea', {
'id': 'cron_status',
'name': 'cron_status',
'style': 'width:30em; padding:5px 10px 5px 10px !important; vertical-align:middle; resize:none !important;',
'readonly': 'readonly',
'wrap': 'off',
'rows': 2,
}, cron_status_string(current_task));
let btn_cron_del = E('button', {
'class': 'cbi-button btn cbi-button-reset',
'id': 'btn_cron_del',
'name': 'btn_cron_del',
}, _('Reset'));
btn_cron_del.onclick = ui.createHandlerFn(this, del_cron_schedule);
btn_cron_del.style.visibility = (current_task) ? 'visible' : 'hidden';
let status_header = E('div', { 'class': 'cbi-section-node' }, [
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'for': 'cron_status' },
_('Current schedule')),
E('div', { 'class': 'cbi-value-field' }, [ cron_status, ' ', btn_cron_del ]),
])
]);
let layout = E('div', { 'class': 'cbi-section-node' });
function layout_append(elem, title, descr) {
descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : '';
layout.append(
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'for': elem.id || null },
title),
E('div', { 'class': 'cbi-value-field' }, [ elem, descr ]),
])
)
};
layout_append(E('b', {}, _('Interval')));
let cron_hour_interval = E('select',
{ 'id': 'cron_hour_interval', 'style': 'width:60px !important; min-width:60px !important' }, [
E('option', { 'value': '' }, ''),
E('option', { 'value': '1' }, '&#8727;')
]);
for(let i = 2; i <= 12 ; i += 2) {
cron_hour_interval.append(E('option', { 'value': String(i) }, '&#8727;/' + i));
};
layout_append(cron_hour_interval, _('Hour'));
cron_hour_interval.onchange = onchange_hour_interval;
let cron_day_interval = E('select',
{ 'id': 'cron_day_interval', 'style': 'width:60px !important; min-width:60px !important' },
E('option', { 'value': '1' }, '&#8727;')
);
for(let i = 2; i < 8 ; i++) {
cron_day_interval.append(E('option', { 'value': String(i) }, '&#8727;/' + i));
};
cron_day_interval.append(E('option', { 'value': '14' }, '&#8727;/14'));
cron_day_interval.append(E('option', { 'value': '28' }, '&#8727;/28'));
layout_append(cron_day_interval, _('Day'));
layout_append(E('b', {}, _('Time')));
let cron_hour = E('select',
{ 'id': 'cron_hour', 'style': 'width:60px !important; min-width:60px !important' });
for(let i = 0; i < 24 ; i++) {
cron_hour.append(E('option', { 'value': String(i) }, to_dd(i)));
};
layout_append(cron_hour, _('Hour'));
let cron_min = E('select',
{ 'id': 'cron_min', 'style': 'width:60px !important; min-width:60px !important' });
for(let i = 0; i < 60 ; i++) {
cron_min.append(E('option', { 'value': String(i) }, to_dd(i)));
};
layout_append(cron_min, _('Minute'));
let btn_cron_add = E('button', {
'class': 'btn cbi-button-save',
'id': 'btn_cron_add',
'name': 'btn_cron_add'
}, _('Set'));
btn_cron_add.onclick = ui.createHandlerFn(this, set_cron_schedule);
layout_append(btn_cron_add);
return E([
E('h2',
{ 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Blacklist updates') + ' (cron)'),
E('div', { 'class': 'cbi-section-descr fade-in' }),
E('div', { 'class': 'cbi-section fade-in' }, status_header),
E('div', { 'class': 'cbi-section fade-in' }, layout),
]);
},
handleSave: null,
handleSaveApply: null,
handleReset: null,
});
@@ -0,0 +1,219 @@
'use strict';
'require fs';
'require ui';
'require view.ruantiblock.tools as tools';
return L.view.extend({
poll_info: function() {
return fs.exec_direct(tools.exec_path, [ 'html-info' ], 'json').catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s<br />[ %s ]'.format(e.message, tools.exec_path)
));
L.Poll.stop();
}).then(data => {
if(!data) {
return;
};
try {
data = JSON.parse(data);
} catch(err) {};
if(data.status === 'enabled') {
let date = document.getElementById('last_blacklist_update.date');
if(data.last_blacklist_update.status) {
if(date) {
date.textContent = data.last_blacklist_update.date;
};
let ip = document.getElementById('last_blacklist_update.ip');
if(ip) {
ip.textContent = data.last_blacklist_update.ip;
};
let cidr = document.getElementById('last_blacklist_update.cidr');
if(cidr) {
cidr.textContent = data.last_blacklist_update.cidr;
};
let fqdn = document.getElementById('last_blacklist_update.fqdn');
if(fqdn) {
fqdn.textContent = data.last_blacklist_update.fqdn;
};
} else {
if(date) {
date.textContent = _('No data');
};
};
if(data.iptables) {
for(let [k, v] of Object.entries(data.iptables)) {
if(k === '_dummy') continue;
let elem = document.getElementById('iptables.' + k);
if(elem) {
elem.textContent = v;
};
};
};
if(data.ipset) {
for(let [k, v] of Object.entries(data.ipset)) {
if(k === '_dummy') continue;
let elem0 = document.getElementById('ipset.' + k + '.' + '0');
let elem1 = document.getElementById('ipset.' + k + '.' + '1');
if(elem0 && elem1) {
elem0.textContent = v[0];
elem1.textContent = v[1];
};
};
};
} else {
if(L.Poll.active()) {
L.Poll.stop();
};
};
});
},
load: function() {
return fs.exec_direct(tools.exec_path, [ 'html-info' ], 'json').catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s<br />[ %s ]'.format(e.message, tools.exec_path)
));
})
},
render: function(data) {
if(!data) {
return;
};
try {
data = JSON.parse(data);
} catch(err) {};
let update_status = null,
iptables = null,
ipset = null;
if(data) {
if(data.status === 'enabled') {
update_status = E('div', { 'class': 'table' });
if(data.last_blacklist_update.status) {
update_status.append(
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '33%' },
_('Last blacklist update')),
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.date' },
data.last_blacklist_update.date),
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '33%' }, 'IP'),
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.ip' },
data.last_blacklist_update.ip),
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '33%' }, 'CIDR'),
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.cidr' },
data.last_blacklist_update.cidr),
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '33%' }, 'FQDN'),
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.fqdn' },
data.last_blacklist_update.fqdn),
])
);
} else {
update_status.append(
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '33%' },
_('Last blacklist update')),
E('div', { 'class': 'td left' }, _('No data')),
])
);
};
if(data.iptables) {
let table_iptables = E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th left', 'width': '33%' },
_('Match-set')),
E('div', { 'class': 'th left' }, _('Bytes')),
]),
]);
for(let [k, v] of Object.entries(data.iptables)) {
if(k === '_dummy') continue;
table_iptables.append(
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '33%' },
k),
E('div', { 'class': 'td left', 'id': 'iptables.' + k },
v),
])
);
};
iptables = E([
E('h3', {}, _('Iptables rules')),
table_iptables,
]);
};
if(data.ipset) {
let table_ipset = E('div', { 'class': 'table' },
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th left', 'width': '33%' }, _('Name')),
E('div', { 'class': 'th left' }, _('Size in memory')),
E('div', { 'class': 'th left' }, _('Number of entries')),
])
);
for(let [k, v] of Object.entries(data.ipset)) {
if(k === '_dummy') continue;
table_ipset.append(
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '33%' }, k),
E('div', { 'class': 'td left', 'id': 'ipset.' + k + '.' + '0' },
v[0]),
E('div', { 'class': 'td left', 'id': 'ipset.' + k + '.' + '1' },
v[1]),
])
);
};
ipset = E([
E('h3', {}, _('Ipset')),
table_ipset,
]);
};
L.Poll.add(this.poll_info);
} else {
update_status = E('em', {}, _('Status') + ' : ' + _('disabled'));
};
};
return E([
E('h2', { 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Statistics')),
E('div', { 'class': 'cbi-section-descr fade-in' }),
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' }, update_status)
),
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' }, iptables)
),
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' }, ipset)
),
]);
},
handleSave: null,
handleSaveApply: null,
handleReset: null,
});
@@ -0,0 +1,201 @@
'use strict';
'require fs';
'require ui';
'require view.ruantiblock.tools as tools';
let log_regexp = new RegExp(`^.*(user\\.notice ${tools.app_name}).*$`, 'gm');
return L.view.extend({
tail_default: 25,
parse_log_data: function(logdata) {
return logdata.trim().match(log_regexp);
},
load: function() {
return Promise.all([
L.resolveDefault(fs.stat('/sbin/logread'), null),
L.resolveDefault(fs.stat('/usr/sbin/logread'), null),
]).then(stat => {
let logger = (stat[0]) ? stat[0].path : (stat[1]) ? stat[1].path : null;
if(logger) {
return fs.exec_direct(logger, [ '-e', tools.app_name ]).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s<br />[ %s ]'.format(e.message, logger)
));
return '';
});
};
});
},
render: function(logdata) {
let nav_btns_top = '120px';
let loglines = this.parse_log_data(logdata);
let log_textarea = E('textarea', {
'id': 'syslog',
'class': 'cbi-input-textarea',
'style': 'width:100% !important; padding: 0 0 0 45px; font-size:12px',
'readonly': 'readonly',
'wrap': 'off',
'rows': this.tail_default,
'spellcheck': 'false',
}, [ loglines.slice(-this.tail_default).join('\n') ]);
let tail_value = E('input', {
'id': 'tail_value',
'name': 'tail_value',
'type': 'text',
'form': 'log_form',
'class': 'cbi-input-text',
'style': 'width:4em !important; min-width:4em !important',
'maxlength': 5,
});
tail_value.value = this.tail_default;
ui.addValidator(tail_value, 'uinteger', true);
let log_filter = E('input', {
'id': 'log_filter',
'name': 'log_filter',
'type': 'text',
'form': 'log_form',
'class': 'cbi-input-text',
'style': 'margin-left:1em !important; width:16em !important; min-width:16em !important',
'placeholder': _('Message filter'),
'data-tooltip': _('Filter messages with a regexp'),
});
let log_form_submit_btn = E('input', {
'type': 'submit',
'form': 'log_form',
'class': 'cbi-button btn',
'style': 'margin-left:1em !important; vertical-align:middle',
'value': _('Apply'),
'click': ev => ev.target.blur(),
});
function set_log_tail(c_arr) {
let tail_num_val = tail_value.value;
if(tail_num_val && tail_num_val > 0 && c_arr) {
return c_arr.slice(-tail_num_val);
};
return c_arr;
}
function set_log_filter(c_arr) {
let f_pattern = log_filter.value;
if(!f_pattern) {
return c_arr;
};
let f_arr = [];
try {
f_arr = c_arr.filter(s => new RegExp(f_pattern.toLowerCase()).test(s.toLowerCase()));
} catch(err) {
if(err.name === 'SyntaxError') {
ui.addNotification(null,
E('p', {}, _('Wrong regular expression') + ': ' + err.message));
return c_arr;
} else {
throw err;
};
};
if(f_arr.length === 0) {
f_arr.push(_('No matches...'));
};
return f_arr;
}
return E([
E('h2', { 'id': 'log_title', 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Log')),
E('div', { 'class': 'cbi-section-descr fade-in' }),
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' },
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'for': 'tail_value' },
_('Show only the last messages')),
E('div', { 'class': 'cbi-value-field' }, [
tail_value,
E('input', {
'type': 'button',
'form': 'log_form',
'class': 'cbi-button btn cbi-button-reset',
'value': 'Χ',
'click': ev => {
tail_value.value = null;
log_form_submit_btn.click();
ev.target.blur();
},
}),
log_filter,
E('input', {
'type': 'button',
'form': 'log_form',
'class': 'cbi-button btn cbi-button-reset',
'value': 'Χ',
'click': ev => {
log_filter.value = null;
log_form_submit_btn.click();
ev.target.blur();
},
}),
log_form_submit_btn,
E('form', {
'id': 'log_form',
'name': 'log_form',
'style': 'display:inline-block; margin-left:1em !important',
'submit': ui.createHandlerFn(this, function(ev) {
ev.preventDefault();
let form_elems = Array.from(document.forms.log_form.elements);
form_elems.forEach(e => e.disabled = true);
return this.load().then(logdata => {
let loglines = set_log_filter(set_log_tail(
this.parse_log_data(logdata)));
log_textarea.value = loglines.join('\n');
}).finally(() => {
form_elems.forEach(e => e.disabled = false);
});
}),
}, E('span', {}, '&#160;')),
]),
])
)
),
E('div', { 'class': 'cbi-section fade-in' },
E('div', { 'class': 'cbi-section-node' },
E('div', { 'class': 'cbi-value' }, [
E('div', { 'style': 'position:fixed' }, [
E('button', {
'class': 'btn',
'style': 'position:relative; display:block; margin:0 !important; left:1px; top:'
+ nav_btns_top,
'click': ev => {
log_textarea.scrollTop = 0;
ev.target.blur();
},
}, '&#8593;'),
E('button', {
'class': 'btn',
'style': 'position:relative; display:block; margin:0 !important; margin-top:1px !important; left:1px; top:'
+ nav_btns_top,
'click': ev => {
log_textarea.scrollTop = log_textarea.scrollHeight;
ev.target.blur();
},
}, '&#8595;'),
]),
log_textarea,
])
)
),
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null,
});
@@ -0,0 +1,347 @@
'use strict';
'require fs';
'require uci';
'require ui';
'require view.ruantiblock.tools as tools';
const btn_style_neutral = 'btn'
const btn_style_action = 'btn cbi-button-action';
const btn_style_save = 'btn cbi-button-save important';
const btn_style_reset = 'btn cbi-button-reset important';
const btn_style_warning = 'btn cbi-button-negative important'
let status_token_value;
function disable_buttons(bool, btn, elems=[]) {
let btn_start = elems[1] || document.getElementById("btn_start");
let btn_destroy = elems[5] || document.getElementById("btn_destroy");
let btn_enable = elems[2] || document.getElementById("btn_enable");
let btn_update = elems[4] || document.getElementById("btn_update");
let btn_tp = elems[3] || document.getElementById("btn_tp");
btn_start.disabled = bool;
btn_update.disabled = bool;
btn_destroy.disabled = bool;
if(btn === btn_update) {
btn_enable.disabled = false;
} else {
btn_enable.disabled = bool;
};
if(btn_tp) {
btn_tp.disabled = bool
};
}
function get_app_status() {
return Promise.all([
fs.exec(tools.exec_path, [ 'raw-status' ]),
fs.exec(tools.exec_path, [ 'total-proxy-status' ]),
fs.exec(tools.exec_path, [ 'vpn-route-status' ]),
fs.exec(tools.init_path, [ 'enabled' ]),
L.resolveDefault(fs.read(tools.token_file), 0),
uci.load(tools.app_name),
]).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s<br />[ %s | %s | %s ]'.format(
e.message, tools.exec_path, tools.init_path, 'uci.ruantiblock'
)));
});
}
function set_app_status(status_array, elems=[], force_app_code) {
let section = uci.get(tools.app_name, 'config');
if(!status_array || typeof(section) !== 'object') {
(elems[0] || document.getElementById("status")).innerHTML = tools.make_status_string(1);
ui.addNotification(null, E('p', _('Unable to read the contents')
+ ': set_app_status()'));
disable_buttons(true, null, elems);
return;
};
let app_status_code = (force_app_code) ? force_app_code : status_array[0].code;
let tp_status_code = status_array[1].code;
let vpn_route_status_code = status_array[2].code;
let enabled_flag = status_array[3].code;
let proxy_local_clients = section.proxy_local_clients;
let proxy_mode = section.proxy_mode;
let bllist_mode = section.bllist_mode;
let bllist_module = section.bllist_module;
let bllist_source = section.bllist_source;
let btn_enable = elems[2] || document.getElementById('btn_enable');
if(enabled_flag == 0) {
btn_enable.onclick = ui.createHandlerFn(this, button_action, 'disable');
btn_enable.textContent = _('Disable');
btn_enable.className = btn_style_reset;
} else {
btn_enable.onclick = ui.createHandlerFn(this, button_action, 'enable');
btn_enable.textContent = _('Enable');
btn_enable.className = btn_style_save;
};
let btn_tp = elems[3] || document.getElementById('btn_tp');
if(btn_tp) {
if(tp_status_code == 0) {
btn_tp.onclick = ui.createHandlerFn(this, button_action, 'total-proxy-off');
btn_tp.textContent = _('Disable');
btn_tp.className = btn_style_reset;
} else {
btn_tp.onclick = ui.createHandlerFn(this, button_action, 'total-proxy-on');
btn_tp.textContent = _('Enable');
btn_tp.className = btn_style_save;
};
};
let btn_start = elems[1] || document.getElementById("btn_start");
let btn_update = elems[4] || document.getElementById("btn_update");
let btn_destroy = elems[5] || document.getElementById("btn_destroy");
function btn_start_state_on() {
btn_start.onclick = ui.createHandlerFn(this, button_action, 'stop');
btn_start.textContent = _('Disable');
btn_start.className = btn_style_reset;
}
function btn_start_state_off() {
btn_start.onclick = ui.createHandlerFn(this, button_action, 'start');
btn_start.textContent = _('Enable');
btn_start.className = btn_style_action;
}
if(app_status_code == 0) {
disable_buttons(false, null, elems);
btn_start_state_on();
btn_destroy.disabled = false;
btn_update.disabled = false;
if(btn_tp) {
btn_tp.disabled = false;
};
}
else if(app_status_code == 2) {
disable_buttons(false, null, elems);
btn_start_state_off();
btn_update.disabled = true;
if(btn_tp) {
btn_tp.disabled = true;
};
}
else if(app_status_code == 3) {
btn_start_state_off();
disable_buttons(true, btn_start, elems);
}
else if(app_status_code == 4) {
btn_start_state_on();
disable_buttons(true, btn_update, elems);
}
else {
ui.addNotification(null, E('p', _('Error')
+ ' %s: return code = %s'.format(tools.exec_path, app_status_code)));
disable_buttons(true, null, elems);
};
(elems[0] || document.getElementById("status")).innerHTML = tools.make_status_string(
app_status_code,
proxy_mode,
bllist_mode,
bllist_module,
bllist_source,
tp_status_code,
vpn_route_status_code);
if(!L.Poll.active()) {
L.Poll.start();
};
}
function button_action(action) {
let btn,
cmd = tools.exec_path;
switch(action) {
case 'start':
case 'stop':
btn = document.getElementById('btn_start');
break;
case 'destroy':
btn = document.getElementById('btn_destroy');
break;
case 'update':
btn = document.getElementById('btn_update');
break;
case 'enable':
case 'disable':
btn = document.getElementById('btn_enable');
cmd = tools.init_path;
break;
case 'total-proxy-on':
case 'total-proxy-off':
btn = document.getElementById('btn_tp');
break;
}
disable_buttons(true, btn);
L.Poll.stop();
if(action === 'update') {
get_app_status().then(status_array => {
set_app_status(status_array, [], 4);
});
};
return fs.exec_direct(cmd, [ action ]).then(res => {
return get_app_status().then(
(status_array) => {
set_app_status(status_array);
ui.hideModal();
});
});
}
return L.view.extend({
poll_status: function() {
return fs.read(tools.token_file).then(v => {
v = tools.normalize_value(v);
if(v != status_token_value) {
get_app_status().then(set_app_status);
}
status_token_value = v;
}).catch(e => {
status_token_value = 0;
});
},
dialog_destroy: 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, function() {
cancel_button.disabled = true;
return button_action('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() {
return get_app_status();
},
render: function(status_array) {
if(!status_array) {
return;
};
let section = uci.get(tools.app_name, 'config');
let proxy_local_clients = (typeof(section) === 'object') ? section.proxy_local_clients : null;
status_token_value = (Array.isArray(status_array)) ? tools.normalize_value(status_array[4]) : null;
document.head.append(E('style', {'type': 'text/css'}, tools.css));
let status_string = E('div', {
'id': 'status',
'name': 'status',
'class': 'cbi-section-node',
});
let layout = E('div', { 'class': 'cbi-section-node' });
function layout_append(elem, title, descr) {
descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : '';
layout.append(
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, title),
E('div', { 'class': 'cbi-value-field' }, [ elem, descr ]),
])
)
};
let btn_start = E('button', {
'id': 'btn_start',
'name': 'btn_start',
'class': btn_style_action,
}, _('Enable'));
layout_append(btn_start, _('Service'));
let btn_enable = E('button', {
'id': 'btn_enable',
'name': 'btn_enable',
'class': btn_style_save,
}, _('Enable'));
layout_append(btn_enable, _('Run at startup'));
let btn_tp = E('button', {
'id': 'btn_tp',
'name': 'btn_tp',
'class': btn_style_save,
}, _('Enable'));
if(proxy_local_clients == '0') {
layout_append(btn_tp, _('Total-proxy'),
_('All traffic goes through the proxy without applying rules'));
};
let btn_update = E('button', {
'id': 'btn_update',
'name': 'btn_update',
'class': btn_style_action,
}, _('Update'));
btn_update.onclick = ui.createHandlerFn(this, () => { button_action('update') });
layout_append(btn_update, _('Update blacklist'));
let btn_destroy = E('button', {
'id': 'btn_destroy',
'name': 'btn_destroy',
'class': btn_style_reset,
}, _('Shutdown'));
btn_destroy.onclick = this.dialog_destroy;
layout_append(btn_destroy, _('Shutdown'),
_('Complete service shutdown, as well as deleting ipsets and blacklist data'));
set_app_status(status_array, [
status_string,
btn_start,
btn_enable,
btn_tp,
btn_update,
btn_destroy,
]);
L.Poll.add(this.poll_status);
return E([
E('h2', { 'class': 'fade-in' }, _('Ruantiblock')),
E('div', { 'class': 'cbi-section-descr fade-in' },
E('a', {
'href': 'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki',
'target': '_blank' },
'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki')
),
E('div', { 'class': 'cbi-section fade-in' }, [
status_string,
E('hr'),
]),
E('div', { 'class': 'cbi-section fade-in' },
layout
),
]);
},
handleSave: null,
handleSaveApply: null,
handleReset: null,
});
@@ -0,0 +1,397 @@
'use strict';
'require fs';
'require uci';
'require form';
'require ui';
'require tools.widgets as widgets';
'require view.ruantiblock.tools as tools';
let available_parsers = [];
function depends(elem, key, array, empty=true) {
if(empty && array.length === 0) {
elem.depends(key, '_dummy');
} else {
array.forEach(e => elem.depends(key, e));
};
};
function depends_bllist_module(elem) {
depends(elem, 'bllist_module', available_parsers);
};
function validate_ip_port(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`;
};
let CBIBlockTitle = form.DummyValue.extend({
string: null,
renderWidget: function(section_id, option_index, cfgvalue) {
this.title = this.description = null;
return E([
E('label', { 'class': 'cbi-value-title' }),
E('div', { 'class': 'cbi-value-field' },
E('b', {}, this.string)
),
]);
},
});
let ip_filter_edit = new tools.file_edit_dialog(
tools.ip_filter_file,
_('IP filter'),
_('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol <code>#</code> in the first position of the line - comments on the line.<br />Examples (dot is a special character):') + '<br /><code>128[.]199[.]0[.]0/16<br />34[.]217[.]90[.]52<br />162[.]13[.]190[.]</code>'
);
let fqdn_filter_edit = new tools.file_edit_dialog(
tools.fqdn_filter_file,
_('FQDN filter'),
_('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol <code>#</code> in the first position of the line - comments on the line.<br />Examples:') + '<br /><code>poker<br />[ck]?a[sz]ino?<br />[vw]ulkan<br />slots?</code>'
);
let user_entries_edit = new tools.file_edit_dialog(
tools.user_entries_file,
_('User 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>'
);
let torrc_edit = new tools.file_edit_dialog(
tools.torrc_file,
_('Tor configuration file'),
null,
function(rc) {
return fs.exec('/etc/init.d/tor', [ 'enabled' ]).then(res => {
if(res.code === 0) {
return fs.exec('/etc/init.d/tor', [ 'restart' ]);
};
}).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s<br />[ %s ]'.format(e.message, '/etc/init.d/tor')));
});
}
);
return L.view.extend({
app_status_code: null,
load: function() {
return Promise.all([
L.resolveDefault(fs.exec(tools.exec_path, [ 'raw-status' ]), 1),
fs.list(tools.parsers_dir),
uci.load('network'),
]).catch(e => {
ui.addNotification(null, E('p', _('Unable to read the contents')
+ ': %s<br />[ %s ]'.format(
e.message, tools.parsers_dir
)));
});
},
render: function(data) {
if(!data) {
return;
};
this.app_status_code = data[0].code;
let p_dir_arr = data[1];
let lan_iface = uci.get('network', 'lan', 'ifname') || 'eth0';
let vpn_iface = uci.get('network', 'VPN', 'ifname') || 'tun0';
if(p_dir_arr) {
p_dir_arr.forEach(e => {
let fname = e.name;
if(fname.startsWith('ruab_parser')) {
available_parsers.push(tools.parsers_dir + '/' + fname);
};
});
};
let m, s, o;
m = new form.Map(tools.app_name, _('Ruantiblock') + ' - ' + _('Settings'));
s = m.section(form.NamedSection, 'config');
s.anonymous = true;
s.addremove = false;
/* Main settings tab */
s.tab('main_settings', _('Main settings'));
// PROXY_MODE
if(this.app_status_code == 1 || this.app_status_code == 2) {
o = s.taboption('main_settings', form.ListValue, 'proxy_mode',
_('Proxy mode'));
o.value('1', 'Tor');
o.value('2', 'VPN');
};
// 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;
proxy_local_clients.default = proxy_local_clients.enabled;
// USE_LOGGER
o = s.taboption('main_settings', form.Flag, 'use_logger',
_('Logging events'));
o.rmempty = false;
o.default = 1;
// DEF_TOTAL_PROXY
o = s.taboption('main_settings', form.Flag, 'def_total_proxy',
_("Enable the 'total-proxy' option at startup"));
o.rmempty = false;
o.default = 0;
o.depends('proxy_local_clients', '0');
// IPSET_CLEAR_SETS
o = s.taboption('main_settings', form.Flag, 'ipset_clear_sets',
_('Clean up ipsets before updating blacklist'));
o.description = _('Reduces RAM consumption during update');
o.rmempty = false;
o.default = 0;
if(this.app_status_code == 1 || this.app_status_code == 2) {
/* Tor tab */
s.tab('tor_settings', _('Tor mode'));
// IF_LAN
o = s.taboption('tor_settings', widgets.DeviceSelect, 'if_lan',
_('LAN interface'));
o.multiple = false;
o.noaliases = true;
o.rmempty = false;
o.default = lan_iface;
// TOR_TRANS_PORT
o = s.taboption('tor_settings', form.Value, 'tor_trans_port',
_('Transparent proxy port for iptables rules'));
o.rmempty = false;
o.datatype = "port";
o.default = '9040';
// ONION_DNS_ADDR
o = s.taboption('tor_settings', form.Value, 'onion_dns_addr',
_("Optional DNS resolver for '.onion' zone"), '<code>ipaddress#port</code>');
o.rmempty = false;
o.default = '127.0.0.1#9053';
o.validate = validate_ip_port;
// Torrc edit dialog
o = s.taboption('tor_settings', form.Button, '_torrc_btn',
_('Tor configuration file'));
o.onclick = () => torrc_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
/* VPN tab */
s.tab('vpn_settings', _('VPN mode'));
// IF_VPN
o = s.taboption('vpn_settings', widgets.DeviceSelect, 'if_vpn',
_('VPN interface'));
o.multiple = false;
o.noaliases = true;
o.rmempty = false;
o.default = vpn_iface;
};
/* Parser settings tab */
s.tab('parser_settings', _('Blacklist settings'));
// BLLIST_MODULE
let bllist_module = s.taboption('parser_settings', form.ListValue,
'bllist_module', _('Blacklist module'));
bllist_module.value("", _("user entries only"));
available_parsers.forEach(e => bllist_module.value(e));
// BLLIST_MODE
let bllist_mode = s.taboption('parser_settings', form.ListValue,
'bllist_mode', _('Module operation mode'));
bllist_mode.value('ip');
bllist_mode.value('fqdn');
depends_bllist_module(bllist_mode);
// BLLIST_SOURCE
let bllist_source = s.taboption('parser_settings', form.ListValue,
'bllist_source', _('Blacklist source'));
bllist_source.description = _("Options") + ':';
for(let [k, v] of Object.entries(tools.blacklist_sources)) {
bllist_source.value(k);
bllist_source.description += `<br />${k} - <a href="${v}" target="_blank">${v}</a>`;
};
depends_bllist_module(bllist_source);
o = s.taboption('parser_settings', CBIBlockTitle, '_dummy_ip');
o.string = _('IP configuration') + ':';
depends_bllist_module(o);
// IP_LIMIT
o = s.taboption('parser_settings', form.Value, 'ip_limit', _("IP limit"));
o.description = _("The number of IP addresses in the subnet, upon reaching which the entire '/24' subnet is added to the list");
o.datatype = 'uinteger';
o.default = '0';
depends_bllist_module(o);
// OPT_EXCLUDE_NETS
o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_nets');
o.title = _('IP subnet patterns (/24) that are excluded from optimization');
o.description = _('ex:') + ' <code>192.168.1.</code>';
o.placeholder = _('ex:') + ' 192.168.1.';
o.default = '';
depends_bllist_module(o);
// SUMMARIZE_IP
o = s.taboption('parser_settings', form.Flag, 'summarize_ip',
_("Summarize IP ranges"));
o.rmempty = false;
o.default = 0;
depends_bllist_module(o);
// SUMMARIZE_CIDR
o = s.taboption('parser_settings', form.Flag, 'summarize_cidr',
_("Summarize '/24' networks"));
o.rmempty = false;
o.default = 0;
depends_bllist_module(o);
o = s.taboption('parser_settings', CBIBlockTitle, '_dummy_fqdn');
o.string = _('FQDN configuration') + ':';
depends_bllist_module(o);
// SD_LIMIT
o = s.taboption('parser_settings', form.Value, 'sd_limit',
_("Subdomains limit"));
o.description = _('The number of subdomains in the domain, upon reaching which the entire 2nd level domain is added to the list');
o.datatype = 'uinteger';
o.default = '16';
depends_bllist_module(o);
// OPT_EXCLUDE_SLD
o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_sld',
_('2nd level domains that are excluded from optimization'));
o.datatype = "hostname";
o.default = [
'livejournal.com',
'facebook.com',
'vk.com',
'blog.jp',
'msk.ru',
'net.ru',
'org.ru',
'net.ua',
'com.ua',
'org.ua',
'co.uk',
'amazonaws.com',
];
depends_bllist_module(o);
// USE_IDN
o = s.taboption('parser_settings', form.Flag, 'use_idn',
_("Convert cyrillic domains to punycode"));
o.rmempty = false;
o.default = 0;
depends_bllist_module(o);
// ALT_NSLOOKUP
o = s.taboption('parser_settings', form.Flag, 'alt_nslookup',
_('Use optional DNS resolver'));
o.rmempty = false;
o.default = 0;
depends_bllist_module(o);
// ALT_DNS_ADDR
o = s.taboption('parser_settings', form.Value, 'alt_dns_addr',
_("Optional DNS resolver"), '<code>ipaddress[#port]</code>');
o.rmempty = false;
o.depends('alt_nslookup', '1');
o.validate = validate_ip_port;
o.default = '8.8.8.8';
/* Entries filters tab */
s.tab('entries_filter_tab', _('Entries filters'));
// IP_FILTER
o = s.taboption('entries_filter_tab', form.Flag, 'ip_filter',
_("Enable IP filter"));
o.description = _('Exclude IP addresses from blacklist by IP filter patterns');
o.rmempty = false;
o.default = 0;
depends_bllist_module(o);
// IP_FILTER edit dialog
o = s.taboption('entries_filter_tab', form.Button, '_ip_filter_btn',
_("IP filter"));
o.onclick = () => ip_filter_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
depends_bllist_module(o);
// FQDN_FILTER
o = s.taboption('entries_filter_tab', form.Flag, 'fqdn_filter',
_("Enable FQDN filter"));
o.description = _('Exclude domains from blacklist by FQDN filter patterns');
o.rmempty = false;
o.default = 0;
depends_bllist_module(o);
// FQDN_FILTER edit dialog
o = s.taboption('entries_filter_tab', form.Button, '_fqdn_filter_btn',
_("FQDN filter"));
o.onclick = () => fqdn_filter_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
depends_bllist_module(o);
/* User entries tab */
s.tab('user_entries_tab', _('User entries'));
// ADD_USER_ENTRIES
o = s.taboption('user_entries_tab', form.Flag, 'add_user_entries',
_('Enable'), _("Add user entries to the blacklist when updating"));
o.rmempty = false;
o.default = 0;
depends_bllist_module(o);
// USER_ENTRIES_DNS
o = s.taboption('user_entries_tab', form.Value, 'user_entries_dns',
_("DNS server that is used for FQDN entries"), '<code>ipaddress[#port]</code>');
o.validate = validate_ip_port;
// USER_ENTRIES edit dialog
o = s.taboption('user_entries_tab', form.Button, '_user_entries_btn',
_('User entries'));
o.onclick = () => user_entries_edit.show();
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
let map_promise = m.render();
map_promise.then(node => node.classList.add('fade-in'));
return map_promise;
},
handleSaveApply: function(ev, mode) {
return this.handleSave(ev).then(() => {
ui.changes.apply(mode == '0');
if(this.app_status_code != 1 && this.app_status_code != 2) {
window.setTimeout(() => fs.exec(tools.init_path, [ 'restart' ]), 3000);
};
});
},
});
@@ -0,0 +1,249 @@
'use strict';
'require fs';
'require ui';
return L.Class.extend({
app_name: 'ruantiblock',
exec_path: '/usr/bin/ruantiblock',
init_path: '/etc/init.d/ruantiblock',
token_file: '/var/run/ruantiblock.token',
parsers_dir: '/usr/bin',
torrc_file: '/etc/tor/torrc',
user_entries_file: '/etc/ruantiblock/user_entries',
fqdn_filter_file: '/etc/ruantiblock/fqdn_filter',
ip_filter_file: '/etc/ruantiblock/ip_filter',
crontab_file: '/etc/crontabs/root',
info_label_starting: '<span class="label-status starting">' + _('Starting') + '</span>',
info_label_running: '<span class="label-status running">' + _('Enabled') + '</span>',
info_label_updating: '<span class="label-status updating">' + _('Updating') + '</span>',
info_label_stopped: '<span class="label-status stopped">' + _('Disabled') + '</span>',
info_label_error: '<span class="label-status error">' + _('Error') + '</span>',
blacklist_sources: {
'rublacklist': 'https://rublacklist.net',
'zapret-info': 'https://github.com/zapret-info/z-i',
'antifilter': 'https://antifilter.download',
},
css: `
.label-status {
display: inline;
margin: 0px 2px 0px 0 !important;
padding: 1px 4px 2px 4px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
font-weight: bold;
color: #fff !important;
}
.starting {
background-color: #a7b668 !important;
}
.running {
background-color: #2ea256 !important;
}
.updating {
background-color: #1e82ff !important;
}
.stopped {
background-color: #acacac !important;
}
.error {
background-color: #ff4e54 !important;
}
.total-proxy {
background-color: #ffb937 !important;
}`,
normalize_value: function(v) {
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
},
make_status_string: function(
app_status_code,
proxy_mode,
bllist_mode,
bllist_module,
bllist_source,
tp_status_code,
vpn_route_status_code) {
let app_status_label;
let spinning = '';
switch(app_status_code) {
case 0:
app_status_label = this.info_label_running;
break;
case 2:
app_status_label = this.info_label_stopped;
break;
case 3:
app_status_label = this.info_label_starting;
spinning = ' spinning';
break;
case 4:
app_status_label = this.info_label_updating;
spinning = ' spinning';
break;
default:
app_status_label = this.info_label_error;
return `<div class="table">
<div class="tr">
<div class="td left" style="width:33%">
${_('Status')}
</div>
<div class="td left">
${app_status_label}
</div>
</div>
</div>`
};
return `<div class="table">
<div class="tr">
<div class="td left" style="width:33%%">
${_('Status')}
</div>
<div class="td left%s">
%s %s %s
</div>
</div>
<div class="tr">
<div class="td left" style="width:33%%">
${_('Proxy mode')}
</div>
<div class="td left">
%s
</div>
</div>
<div class="tr">
<div class="td left" style="width:33%%">
${_('Blacklist update mode')}
</div>
<div class="td left">
%s
</div>
</div>
%s
</div>
`.format(
spinning,
app_status_label,
(tp_status_code == 0) ? '<span class="label-status total-proxy">'
+ _('Total-proxy is on') + '</span>' : '',
(app_status_code != 2 && proxy_mode == 2 && vpn_route_status_code != 0)
? '<span class="label-status error">'
+ _('VPN routing error! Need restart') + '</span>' : '',
(proxy_mode == 1) ? 'Tor' : 'VPN',
(!bllist_module || bllist_module === '') ? _('user entries only') : bllist_mode,
(!bllist_module || bllist_module === '') ? '' : `<div class="tr">
<div class="td left" style="width:33%%">
${_('Blacklist source')}
</div>
<div class="td left">
<span style="cursor:help; border-bottom:1px dotted" data-tooltip="${this.blacklist_sources[bllist_source]}">
${bllist_source}
</span>
</div>
</div>`
);
},
file_edit_dialog: L.Class.extend({
__init__: function(file, title, description, callback, file_exists=false) {
this.file = file;
this.title = title;
this.description = description;
this.callback = callback;
this.file_exists = file_exists;
},
load: function() {
return fs.read(this.file);
},
render: function(content) {
ui.showModal(this.title, [
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'cbi-section-descr' }, this.description),
E('div', { 'class': 'cbi-section' },
E('p', {},
E('textarea', {
'id': 'widget.modal_content',
'class': 'cbi-input-textarea',
'style': 'width:100% !important',
'rows': 10,
'wrap': 'off',
'spellcheck': 'false',
},
content || '')
)
),
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal,
}, _('Dismiss')),
' ',
E('button', {
'id': 'btn_save',
'class': 'btn cbi-button-positive important',
'click': ui.createHandlerFn(this, this.handleSave),
}, _('Save')),
]),
]);
},
handleSave: function(ev) {
let textarea = document.getElementById('widget.modal_content');
let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n';
return fs.write(this.file, value).then(async rc => {
textarea.value = value;
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)));
}).finally(() => {
ui.hideModal();
});
},
error: function(e) {
if(!this.file_exists && e instanceof Error && e.name === 'NotFoundError') {
return this.render();
} else {
ui.showModal(this.title, [
E('div', { 'class': 'cbi-section' },
E('p', {}, _('Unable to read the contents')
+ ': %s'.format(e.message))
),
E('div', { 'class': 'right' },
E('button', {
'class': 'btn',
'click': ui.hideModal,
}, _('Dismiss'))
),
]);
};
},
show: function() {
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);
})
},
}),
});
@@ -0,0 +1,51 @@
'use strict';
'require fs';
'require uci';
'require view.ruantiblock.tools as tools';
return L.Class.extend({
title: _('Ruantiblock'),
load: function() {
return Promise.all([
fs.exec(tools.exec_path, [ 'raw-status' ]),
fs.exec(tools.exec_path, [ 'total-proxy-status' ]),
fs.exec(tools.exec_path, [ 'vpn-route-status' ]),
uci.load('ruantiblock'),
]).catch(e => {});
},
render: function(status_array) {
if(!status_array) {
return E('em', _('Error') + ': ' + _('Unable to execute or read contents'));
};
let app_status_code = status_array[0].code;
let tp_status_code = status_array[1].code;
let vpn_route_status_code = status_array[2].code;
let section = uci.get('ruantiblock', 'config');
let proxy_local_clients, proxy_mode, bllist_mode, bllist_module, bllist_source;
if(typeof(section) === 'object') {
proxy_local_clients = section.proxy_local_clients;
proxy_mode = section.proxy_mode;
bllist_mode = section.bllist_mode;
bllist_module = section.bllist_module;
bllist_source = section.bllist_source;
} else {
return _('Error');
};
document.head.append(E('style', { 'type': 'text/css' }, tools.css));
return E('div', { 'class': 'cbi-section' }).innerHTML = tools.make_status_string(
app_status_code,
proxy_mode,
bllist_mode,
bllist_module,
bllist_source,
tp_status_code,
vpn_route_status_code);
},
});
@@ -0,0 +1,13 @@
module('luci.controller.ruantiblock', package.seeall)
function index()
if nixio.fs.access('/etc/config/ruantiblock') and nixio.fs.access('/usr/bin/ruantiblock') then
entry({'admin', 'services', 'ruantiblock'}, firstchild(), _('Ruantiblock'), 60).acl_depends = { 'luci-app-ruantiblock' }
entry({'admin', 'services', 'ruantiblock', 'service'}, view('ruantiblock/service'), _('Service'), 10)
entry({'admin', 'services', 'ruantiblock', 'settings'}, view('ruantiblock/settings'), _('Settings'), 20)
entry({'admin', 'services', 'ruantiblock', 'cron'}, view('ruantiblock/cron'), _('Blacklist updates'), 30)
entry({'admin', 'services', 'ruantiblock', 'info'}, view('ruantiblock/info'), _('Statistics'), 40)
entry({'admin', 'services', 'ruantiblock', 'log'}, view('ruantiblock/log'), _('Log'), 50)
end
end
+373
View File
@@ -0,0 +1,373 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"X-Generator: Poedit 2.0.6\n"
msgid "valid IP address"
msgstr "верный IP-адрес"
msgid "valid address#port"
msgstr "верный IP-адрес#порт"
msgid "2nd level domains that are excluded from optimization"
msgstr "Домены 2-го уровня не подлежащие оптимизации"
msgid "Add user entries to the blacklist when updating"
msgstr "Добавлять записи пользователя в блэклист при обновлении"
msgid "All traffic goes through the proxy without applying rules"
msgstr "Весь трафик отправляется в прокси, без применения правил"
msgid "Apply"
msgstr "Применить"
msgid "Apply proxy rules to router application traffic"
msgstr "Применять правила прокси к трафику приложений роутера"
msgid "Blacklist module"
msgstr "Модуль блэклиста"
msgid "Blacklist settings"
msgstr "Настройки блэклиста"
msgid "Blacklist source"
msgstr "Источник блэклиста"
msgid "Blacklist update mode"
msgstr "Режим обновления блэклиста"
msgid "Blacklist updates"
msgstr "Обновления блэклиста"
msgid "Bytes"
msgstr "Байты"
msgid "Cancel"
msgstr "Отмена"
msgid "Changes have been saved."
msgstr "Изменения сохранены."
msgid "Clean up ipsets before updating blacklist"
msgstr "Очищать ipset'ы перед обновлением блэклиста"
msgid ""
"Complete service shutdown, as well as deleting ipsets and blacklist data"
msgstr ""
"Полное выключение службы, а также удаление ipset'ов и данных блэклиста"
msgid "Contents have been saved."
msgstr "Содержимое сохранено."
msgid "Convert cyrillic domains to punycode"
msgstr "Конвертировать кириллические домены в punycode"
msgid "Current schedule"
msgstr "Текущее расписание"
msgid "DNS server that is used for FQDN entries"
msgstr "DNS сервер для FQDN записей"
msgid "Day"
msgstr "День"
msgid "Disable"
msgstr "Отключить"
msgid "Disabled"
msgstr "Отключено"
msgid "Dismiss"
msgstr "Закрыть"
msgid "Edit"
msgstr "Изменить"
msgid "Enable"
msgstr "Включить"
msgid "Enable FQDN filter"
msgstr "Включить фильтр FQDN"
msgid "Enable IP filter"
msgstr "Включить фильтр IP"
msgid "Enable the 'total-proxy' option at startup"
msgstr "Включать 'total-proxy' при старте"
msgid "Enabled"
msgstr "Включено"
msgid "Entries filters"
msgstr "Фильтры записей"
msgid "Error"
msgstr "Ошибка"
msgid "Exclude domains from blacklist by FQDN filter patterns"
msgstr "Исключение доменов из блэклиста по шаблонам фильтра FQDN"
msgid "Exclude IP addresses from blacklist by IP filter patterns"
msgstr "Исключение IP адресов из блэклиста по шаблонам фильтра IP"
msgid "Expecting:"
msgstr "Ожидается:"
msgid "Filter messages with a regexp"
msgstr "Фильтровать сообщения с помощью регулярного выражения"
msgid "FQDN configuration"
msgstr "Конфигурация FQDN"
msgid "FQDN filter"
msgstr "Фильтр FQDN"
msgid "Hour"
msgstr "Час"
msgid "Interval"
msgstr "Интервал"
msgid "IP configuration"
msgstr "Конфигурация IP"
msgid "IP filter"
msgstr "Фильтр IP"
msgid "IP limit"
msgstr "Лимит IP адресов"
msgid "IP subnet patterns (/24) that are excluded from optimization"
msgstr "Шаблоны IP подсетей (/24) не подлежащих оптимизации"
msgid "Ipset"
msgstr "Ipset"
msgid "Iptables rules"
msgstr "Правила iptables"
msgid "LAN interface"
msgstr "LAN интерфейс"
msgid "Last blacklist update"
msgstr "Последнее обновление блэклиста"
msgid "Loading"
msgstr "Загрузка"
msgid "Log"
msgstr "Лог"
msgid "Logging events"
msgstr "Записывать события в лог"
msgid "Main settings"
msgstr "Основные настройки"
msgid "Match-set"
msgstr "Правило"
msgid "Message filter"
msgstr "Фильтр сообщений"
msgid "Minute"
msgstr "Минута"
msgid "Module operation mode"
msgstr "Режим работы модуля"
msgid "Name"
msgstr "Имя"
msgid "No changes to save."
msgstr "Нет изменений для сохранения."
msgid "No data"
msgstr "Нет данных"
msgid "No matches..."
msgstr "Нет совпадений..."
msgid "No Shedule"
msgstr "Нет расписания"
msgid "Number of entries"
msgstr "Кол-во записей"
msgid ""
"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:"
msgstr ""
"Одна запись (IP, CIDR или FQDN) на строку. В записях FQDN можно задать DNS-"
"сервер для разрешения данного домена (через пробел). Также можно "
"комментировать строки (<code>#</code> - первый символ строки).<br />Примеры:"
msgid "One of the following:"
msgstr "Одно из следующих значений:"
msgid "Only messages that include the specified string will be displayed"
msgstr "Будут показаны только сообщения включающие указанную строку"
msgid "Optional DNS resolver for '.onion' zone"
msgstr "Дополнительный DNS резолвер для '.onion' зоны"
msgid "Optional DNS resolver"
msgstr "Альтернативный DNS резолвер"
msgid "Options"
msgstr "Опции"
msgid ""
"Patterns can be strings or regular expressions. Each pattern in a separate "
"line, the symbol <code>#</code> in the first position of the line - comments "
"on the line.<br />Examples (dot is a special character):"
msgstr ""
"Шаблоны могут быть строками или регулярными выражениями. Каждый шаблон в "
"отдельной строке, символ <code>#</code> в первой позиции строки - "
"комментирует строку.<br />Примеры (точка - спецсимвол):"
msgid ""
"Patterns can be strings or regular expressions. Each pattern in a separate "
"line, the symbol <code>#</code> in the first position of the line - comments "
"on the line.<br />Examples:"
msgstr ""
"Шаблоны могут быть строками или регулярными выражениями. Каждый шаблон в "
"отдельной строке, символ <code>#</code> в первой позиции строки - "
"комментирует строку.<br />Примеры:"
msgid "Proxy mode"
msgstr "Режим прокси"
msgid "Reduces RAM consumption during update"
msgstr "Уменьшает потребление опреративной памяти при обновлении"
msgid "Reset"
msgstr "Сбросить"
msgid "Run at startup"
msgstr "Запуск при старте системы"
msgid "Service"
msgstr "Служба"
msgid "Set"
msgstr "Установить"
msgid "Settings"
msgstr "Настройки"
msgid "Show only the last messages"
msgstr "Показать только последние сообщения"
msgid "Shutdown"
msgstr "Выключение"
msgid "Size in memory"
msgstr "Размер в памяти"
msgid "Starting"
msgstr "Запускается"
msgid "Statistics"
msgstr "Статистика"
msgid "Status"
msgstr "Статус"
msgid "Subdomains limit"
msgstr "Лимит субдоменов"
msgid "Summarize '/24' networks"
msgstr "Суммировать '/24' подсети"
msgid "Summarize IP ranges"
msgstr "Суммировать IP диапазоны"
msgid ""
"The number of IP addresses in the subnet, upon reaching which the entire "
"'/24' subnet is added to the list"
msgstr ""
"Количество IP адресов подсети, при достижении которого в список добавляется "
"вся '/24' подсеть"
msgid ""
"The number of subdomains in the domain, upon reaching which the entire 2nd "
"level domain is added to the list"
msgstr ""
"Количество субдоменов при достижении которого в список добавляется весь "
"домен 2-го уровня"
msgid ""
"The service will be disabled and all blacklist data will be deleted. "
"Continue?"
msgstr ""
"Служба будет выключена и все данные блэклиста будут удалены. Продолжить?"
msgid "Time"
msgstr "Время"
msgid "Tor configuration file"
msgstr "Конфигурационный файл Tor"
msgid "Tor mode"
msgstr "Режим Tor"
msgid "Total-proxy is on"
msgstr "Total-proxy включен"
msgid "Transparent proxy port for iptables rules"
msgstr "Порт прозрачного прокси для правил iptables"
msgid "Unable to execute or read contents"
msgstr "Невозможно выполнить или прочитать содержимое"
msgid "Unable to read the contents"
msgstr "Невозможно прочитать содержимое"
msgid "Unable to save the changes"
msgstr "Невозможно сохранить изменения"
msgid "Unable to save the contents"
msgstr "Невозможно сохранить содержимое"
msgid "Update"
msgstr "Обновить"
msgid "Update blacklist"
msgstr "Обновить блэклист"
msgid "Updating"
msgstr "Обновление"
msgid "Use optional DNS resolver"
msgstr "Использовать альтернативный DNS резолвер"
msgid "User entries"
msgstr "Записи пользователя"
msgid "VPN interface"
msgstr "VPN интерфейс"
msgid "VPN mode"
msgstr "Режим VPN"
msgid "VPN routing error! Need restart"
msgstr "Ошибка маршрутизации VPN! Необходим перезапуск"
msgid "ex:"
msgstr "прим:"
msgid "user entries only"
msgstr "только записи пользователя"
@@ -0,0 +1,343 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "valid IP address"
msgstr ""
msgid "valid address#port"
msgstr ""
msgid "2nd level domains that are excluded from optimization"
msgstr ""
msgid "Add user entries to the blacklist when updating"
msgstr ""
msgid "All traffic goes through the proxy without applying rules"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Apply proxy rules to router application traffic"
msgstr ""
msgid "Blacklist module"
msgstr ""
msgid "Blacklist settings"
msgstr ""
msgid "Blacklist source"
msgstr ""
msgid "Blacklist update mode"
msgstr ""
msgid "Blacklist updates"
msgstr ""
msgid "Bytes"
msgstr ""
msgid "Cancel"
msgstr ""
msgid "Changes have been saved."
msgstr ""
msgid "Clean up ipsets before updating blacklist"
msgstr ""
msgid ""
"Complete service shutdown, as well as deleting ipsets and blacklist data"
msgstr ""
msgid "Contents have been saved."
msgstr ""
msgid "Convert cyrillic domains to punycode"
msgstr ""
msgid "Current schedule"
msgstr ""
msgid "DNS server that is used for FQDN entries"
msgstr ""
msgid "Day"
msgstr ""
msgid "Disable"
msgstr ""
msgid "Disabled"
msgstr ""
msgid "Dismiss"
msgstr ""
msgid "Edit"
msgstr ""
msgid "Enable"
msgstr ""
msgid "Enable FQDN filter"
msgstr ""
msgid "Enable ip filter"
msgstr ""
msgid "Enable the 'total-proxy' option at startup"
msgstr ""
msgid "Enabled"
msgstr ""
msgid "Entries filters"
msgstr ""
msgid "Error"
msgstr ""
msgid "Exclude domains from blacklist by FQDN filter patterns"
msgstr ""
msgid "Exclude IP addresses from blacklist by IP filter patterns"
msgstr ""
msgid "Expecting:"
msgstr ""
msgid "Filter messages with a regexp"
msgstr ""
msgid "FQDN configuration"
msgstr ""
msgid "FQDN filter"
msgstr ""
msgid "Hour"
msgstr ""
msgid "Interval"
msgstr ""
msgid "IP configuration"
msgstr ""
msgid "IP filter"
msgstr ""
msgid "IP limit"
msgstr ""
msgid "IP subnet patterns (/24) that are excluded from optimization"
msgstr ""
msgid "Ipset"
msgstr ""
msgid "Iptables rules"
msgstr ""
msgid "LAN interface"
msgstr ""
msgid "Last blacklist update"
msgstr ""
msgid "Loading"
msgstr ""
msgid "Log"
msgstr ""
msgid "Logging events"
msgstr ""
msgid "Main settings"
msgstr ""
msgid "Match-set"
msgstr ""
msgid "Message filter"
msgstr ""
msgid "Minute"
msgstr ""
msgid "Module operation mode"
msgstr ""
msgid "Name"
msgstr ""
msgid "No changes to save."
msgstr ""
msgid "No data"
msgstr ""
msgid "No matches..."
msgstr ""
msgid "No Shedule"
msgstr ""
msgid "Number of entries"
msgstr ""
msgid ""
"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:"
msgstr ""
msgid "One of the following:"
msgstr ""
msgid "Optional DNS resolver for '.onion' zone"
msgstr ""
msgid "Optional DNS resolver"
msgstr ""
msgid "Options"
msgstr ""
msgid ""
"Patterns can be strings or regular expressions. Each pattern in a separate "
"line, the symbol <code>#</code> in the first position of the line - comments "
"on the line.<br />Examples (dot is a special character):"
msgstr ""
msgid ""
"Patterns can be strings or regular expressions. Each pattern in a separate "
"line, the symbol <code>#</code> in the first position of the line - comments "
"on the line.<br />Examples:"
msgstr ""
msgid "Proxy mode"
msgstr ""
msgid "Reduces RAM consumption during update"
msgstr ""
msgid "Reset"
msgstr ""
msgid "Run at startup"
msgstr ""
msgid "Service"
msgstr ""
msgid "Set"
msgstr ""
msgid "Settings"
msgstr ""
msgid "Show only the last messages"
msgstr ""
msgid "Shutdown"
msgstr ""
msgid "Size in memory"
msgstr ""
msgid "Starting"
msgstr ""
msgid "Statistics"
msgstr ""
msgid "Status"
msgstr ""
msgid "Subdomains limit"
msgstr ""
msgid "Summarize '/24' networks"
msgstr ""
msgid "Summarize IP ranges"
msgstr ""
msgid ""
"The number of IP addresses in the subnet, upon reaching which the entire "
"'/24' subnet is added to the list"
msgstr ""
msgid ""
"The number of subdomains in the domain, upon reaching which the entire 2nd "
"level domain is added to the list"
msgstr ""
msgid ""
"The service will be disabled and all blacklist data will be deleted. "
"Continue?"
msgstr ""
msgid "Time"
msgstr ""
msgid "Tor configuration file"
msgstr ""
msgid "Tor mode"
msgstr ""
msgid "Total-proxy is on"
msgstr ""
msgid "Transparent proxy port for iptables rules"
msgstr ""
msgid "Unable to execute or read contents"
msgstr ""
msgid "Unable to read the contents"
msgstr ""
msgid "Unable to save the changes"
msgstr ""
msgid "Unable to save the contents"
msgstr ""
msgid "Update"
msgstr ""
msgid "Update blacklist"
msgstr ""
msgid "Updating"
msgstr ""
msgid "Use optional DNS resolver"
msgstr ""
msgid "User entries"
msgstr ""
msgid "VPN interface"
msgstr ""
msgid "VPN mode"
msgstr ""
msgid "VPN routing error! Need restart"
msgstr ""
msgid "ex:"
msgstr ""
msgid "user entries only"
msgstr ""
@@ -0,0 +1,63 @@
{
"admin/services/ruantiblock": {
"title": "Ruantiblock",
"order": 60,
"action": {
"type": "alias",
"path": "admin/services/ruantiblock/service"
},
"depends": {
"acl": [ "luci-app-ruantiblock" ],
"fs": {
"/usr/bin/ruantiblock": "executable",
"/etc/init.d/ruantiblock": "executable"
},
"uci": { "ruantiblock": true }
}
},
"admin/services/ruantiblock/service": {
"title": "Service",
"order": 10,
"action": {
"type": "view",
"path": "ruantiblock/service"
}
},
"admin/services/ruantiblock/settings": {
"title": "Settings",
"order": 20,
"action": {
"type": "view",
"path": "ruantiblock/settings"
}
},
"admin/services/ruantiblock/cron": {
"title": "Blacklist updates",
"order": 30,
"action": {
"type": "view",
"path": "ruantiblock/cron"
}
},
"admin/services/ruantiblock/info": {
"title": "Statistics",
"order": 40,
"action": {
"type": "view",
"path": "ruantiblock/info"
}
},
"admin/services/ruantiblock/log": {
"title": "Log",
"order": 50,
"action": {
"type": "view",
"path": "ruantiblock/log"
}
}
}
@@ -0,0 +1,37 @@
{
"luci-app-ruantiblock": {
"description": "Grant access to ruantiblock procedures",
"read": {
"cgi-io": [ "exec" ],
"file": {
"/usr/bin": [ "list" ],
"/etc/ruantiblock/fqdn_filter": [ "read" ],
"/etc/ruantiblock/ip_filter": [ "read" ],
"/etc/ruantiblock/user_entries": [ "read" ],
"/var/run/ruantiblock.token": [ "read" ],
"/etc/tor/torrc": [ "read" ],
"/etc/crontabs/root": [ "read" ],
"/usr/bin/ruantiblock": [ "exec" ],
"/etc/init.d/ruantiblock": [ "exec" ],
"/etc/init.d/tor enabled": [ "exec" ],
"/etc/init.d/tor restart": [ "exec" ],
"/etc/init.d/cron enabled": [ "exec" ],
"/etc/init.d/cron enable": [ "exec" ],
"/etc/init.d/cron restart": [ "exec" ],
"/sbin/logread -e ruantiblock": [ "exec" ],
"/usr/sbin/logread -e ruantiblock": [ "exec" ]
},
"uci": [ "network", "ruantiblock" ]
},
"write": {
"file": {
"/etc/ruantiblock/fqdn_filter": [ "write" ],
"/etc/ruantiblock/ip_filter": [ "write" ],
"/etc/ruantiblock/user_entries": [ "write" ],
"/etc/tor/torrc": [ "write" ],
"/etc/crontabs/root": [ "write" ]
},
"uci": [ "ruantiblock" ]
}
}
}