mirror of
https://github.com/gSpotx2f/ruantiblock_openwrt.git
synced 2026-05-14 06:30:59 +00:00
Initial commit
This commit is contained in:
@@ -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' }, '∗')
|
||||
]);
|
||||
for(let i = 2; i <= 12 ; i += 2) {
|
||||
cron_hour_interval.append(E('option', { 'value': String(i) }, '∗/' + 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' }, '∗')
|
||||
);
|
||||
for(let i = 2; i < 8 ; i++) {
|
||||
cron_day_interval.append(E('option', { 'value': String(i) }, '∗/' + i));
|
||||
};
|
||||
cron_day_interval.append(E('option', { 'value': '14' }, '∗/14'));
|
||||
cron_day_interval.append(E('option', { 'value': '28' }, '∗/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', {}, ' ')),
|
||||
]),
|
||||
])
|
||||
)
|
||||
),
|
||||
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();
|
||||
},
|
||||
}, '↑'),
|
||||
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();
|
||||
},
|
||||
}, '↓'),
|
||||
]),
|
||||
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);
|
||||
})
|
||||
},
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user