luci-app: Minor fixes and improvements

This commit is contained in:
gSpot
2021-11-04 18:57:08 +03:00
parent 87abb0e334
commit 50a934702e
27 changed files with 1659 additions and 1593 deletions
@@ -139,15 +139,15 @@ return L.Class.extend({
* View name (for local storage and downloads).
* Must be overridden by a subclass!
*/
viewName: null,
viewName : null,
/**
* Page title.
* Must be overridden by a subclass!
*/
title: null,
title : null,
logLevels: {
logLevels : {
'emerg': E('span', { 'class': 'zonebadge log-emerg' }, E('strong', _('Emergency'))),
'alert': E('span', { 'class': 'zonebadge log-alert' }, E('strong', _('Alert'))),
'crit': E('span', { 'class': 'zonebadge log-crit' }, E('strong', _('Critical'))),
@@ -158,25 +158,25 @@ return L.Class.extend({
'debug': E('span', { 'class': 'zonebadge log-debug' }, E('strong', _('Debug'))),
},
tailValue: 25,
tailValue : 25,
logSortingValue: 'asc',
logSortingValue : 'asc',
isHosts: false,
isHosts : false,
isLevels: false,
isLevels : false,
logHosts: {},
logHosts : {},
logLevelsStat: {},
logLevelsStat : {},
logHostsDropdown: null,
logHostsDropdown : null,
logLevelsDropdown: null,
totalLogLines: 0,
totalLogLines : 0,
htmlEntities: function(str) {
htmlEntities : function(str) {
return String(str).replace(
/&/g, '&').replace(
/</g, '&#60;').replace(
@@ -426,15 +426,15 @@ return L.Class.extend({
render: function(logdata) {
let logWrapper = E('div', {
'id': 'logWrapper',
'id' : 'logWrapper',
'style': 'width:100%; min-height:20em; padding: 0 0 0 45px; font-size:0.9em !important'
}, this.makeLogArea(this.parseLogData(logdata, this.tailValue)));
let tailInput = E('input', {
'id': 'tailInput',
'name': 'tailInput',
'type': 'text',
'form': 'logForm',
'id' : 'tailInput',
'name' : 'tailInput',
'type' : 'text',
'form' : 'logForm',
'class': 'cbi-input-text',
'style': 'width:4em !important; min-width:4em !important',
'maxlength': 5,
@@ -443,8 +443,8 @@ return L.Class.extend({
ui.addValidator(tailInput, 'uinteger', true);
let tailReset = E('input', {
'type': 'button',
'form': 'logForm',
'type' : 'button',
'form' : 'logForm',
'class': 'cbi-button btn cbi-button-reset',
'value': 'Χ',
'click': ev => {
@@ -455,27 +455,27 @@ return L.Class.extend({
'style': 'max-width:4em !important',
});
let logHostsDropdownElem = '';
let logHostsDropdownElem = '';
let logLevelsDropdownElem = '';
if(this.isLevels) {
logLevelsDropdownElem = this.makeLogLevelsDropdownSection();
};
if(this.isHosts) {
logHostsDropdownElem = this.makeLogHostsDropdownSection();
logHostsDropdownElem = this.makeLogHostsDropdownSection();
};
let logFilter = E('input', {
'id': 'logFilter',
'name': 'logFilter',
'type': 'text',
'form': 'logForm',
'class': 'cbi-input-text',
'id' : 'logFilter',
'name' : 'logFilter',
'type' : 'text',
'form' : 'logForm',
'class' : 'cbi-input-text',
'placeholder': _('Type an expression...'),
});
let logFormSubmitBtn = E('input', {
'type': 'submit',
'form': 'logForm',
'type' : 'submit',
'form' : 'logForm',
'class': 'cbi-button btn cbi-button-action',
'value': _('Apply'),
'click': ev => ev.target.blur(),
@@ -483,9 +483,9 @@ return L.Class.extend({
});
let logSorting = E('select', {
'id': 'logSorting',
'name': 'logSorting',
'form': 'logForm',
'id' : 'logSorting',
'name' : 'logSorting',
'form' : 'logForm',
'class': "cbi-input-select",
}, [
E('option', { 'value': 'asc' }, _('ascending')),
@@ -494,8 +494,8 @@ return L.Class.extend({
logSorting.value = this.logSortingValue;
let logDownloadBtn = E('button', {
'id': 'logDownloadBtn',
'name': 'logDownloadBtn',
'id' : 'logDownloadBtn',
'name' : 'logDownloadBtn',
'class': 'cbi-button btn',
'click': ui.createHandlerFn(this, this.downloadLog),
}, _('Download log'));
@@ -509,7 +509,7 @@ return L.Class.extend({
E('div', { 'id': 'tailInputSection', 'class': 'cbi-value' }, [
E('label', {
'class': 'cbi-value-title',
'for': 'tailInput',
'for' : 'tailInput',
}, _('Last entries')),
E('div', { 'class': 'cbi-value-field' }, [
tailInput,
@@ -523,7 +523,7 @@ return L.Class.extend({
E('div', { 'class': 'cbi-value' }, [
E('label', {
'class': 'cbi-value-title',
'for': 'logFilter',
'for' : 'logFilter',
}, _('Message filter')),
E('div', { 'class': 'cbi-value-field' }, logFilter),
]),
@@ -531,7 +531,7 @@ return L.Class.extend({
E('div', { 'class': 'cbi-value' }, [
E('label', {
'class': 'cbi-value-title',
'for': 'logSorting',
'for' : 'logSorting',
}, _('Sorting entries')),
E('div', { 'class': 'cbi-value-field' }, logSorting,),
]),
@@ -539,14 +539,14 @@ return L.Class.extend({
E('div', { 'class': 'cbi-value' }, [
E('label', {
'class': 'cbi-value-title',
'for': 'logFilter',
'for' : 'logFilter',
}, _('Refresh log')),
E('div', { 'class': 'cbi-value-field' }, [
logFormSubmitBtn,
E('form', {
'id': 'logForm',
'name': 'logForm',
'style': 'display:inline-block; margin-top:0.5em',
'id' : 'logForm',
'name' : 'logForm',
'style' : 'display:inline-block; margin-top:0.5em',
'submit': ui.createHandlerFn(this, function(ev) {
ev.preventDefault();
let formElems = Array.from(document.forms.logForm.elements);
@@ -633,8 +633,8 @@ return L.Class.extend({
]);
},
handleSave : null,
handleSaveApply: null,
handleSave: null,
handleReset: null,
handleReset : null,
}),
})
@@ -3,221 +3,234 @@
'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;
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
)));
});
},
crontabRegexp: new RegExp(
`^(\\*?\\/?(\\d){0,2}\\s){5}${tools.execPath} update(\n)?`, 'gm'),
render: function(content) {
current_crontab_content = content;
let current_task = pick_cron_task(content);
currentCrontabContent: null,
let cron_status = E('textarea', {
'id': 'cron_status',
'name': 'cron_status',
'style': 'width:100% !important; padding:5px 10px 5px 10px !important; resize:none !important;',
'readonly': 'readonly',
'wrap': 'off',
'rows': 2,
}, cron_status_string(current_task));
toDD: function(n){
return String(n).replace(/^(\d)$/, "0$1");
},
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';
cronStatusString: function(s) {
return s || _('No Shedule');
},
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),
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'for': 'btn_cron_del' }),
E('div', { 'class': 'cbi-value-field' }, btn_cron_del),
])
]);
pickCronTask: function(content) {
if(!content){
return;
};
let current_tasks = content.match(this.crontabRegexp) || [];
return current_tasks.join('');
},
let layout = E('div', { 'class': 'cbi-section-node' });
setCronStatus: function(value) {
document.getElementById('cron_status').value = this.cronStatusString(value);
document.getElementById("btn_cron_del").style.visibility = (value) ?
'visible' : 'hidden';
},
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 ]),
])
)
};
writeCronFile: function() {
let btn_cron_add = document.getElementById('btn_cron_add');
let btn_cron_del = document.getElementById('btn_cron_del');
layout_append(E('b', {}, _('Interval')));
if(!this.currentCrontabContent) {
ui.addNotification(null, E('p', _('No changes to save.')));
btn_cron_add.disabled = false;
return;
};
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;
return fs.write(tools.crontabFile, this.currentCrontabContent).then(rc => {
ui.addNotification(null, E('p',_('Changes have been saved.')), 'info');
this.setCronStatus(this.pickCronTask(this.currentCrontabContent));
}).then(() => {
return tools.getInitStatus('cron').then(res => {
if(!res) {
return tools.handleServiceAction('cron', 'enable');
};
});
}).finally(() => {
return tools.handleServiceAction('cron', 'restart');
}).catch(e => {
ui.addNotification(null, E('p', _('Unable to save the changes')
+ ': %s [ %s ]'.format(
e.message, tools.crontabFile
)));
});
},
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'));
delCronSchedule: function(ev) {
if(this.currentCrontabContent) {
this.currentCrontabContent = this.currentCrontabContent.replace(
this.crontabRegexp, "");
};
return this.writeCronFile();
},
layout_append(E('b', {}, _('Time')));
setCronSchedule: function(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.execPath
);
if(this.currentCrontabContent) {
this.currentCrontabContent = this.currentCrontabContent.replace(
this.crontabRegexp, "") + task_string;
};
return this.writeCronFile();
},
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'));
onchangeHourInterval: function(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;
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'));
// 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%';
};
},
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);
load: function() {
return fs.read(tools.crontabFile).catch(e => {
ui.addNotification(null, E('p', _('Unable to read the contents')
+ ': %s [ %s ]'.format(
e.message, tools.crontabFile
)));
});
},
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),
]);
render: function(content) {
this.currentCrontabContent = content;
let current_task = this.pickCronTask(content);
},
let cron_status = E('textarea', {
'id': 'cron_status',
'name': 'cron_status',
'style': 'width:100% !important; padding:5px 10px 5px 10px !important; resize:none !important;',
'readonly': 'readonly',
'wrap': 'off',
'rows': 2,
}, this.cronStatusString(current_task));
handleSave: null,
handleSaveApply: null,
handleReset: null,
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, this.delCronSchedule);
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),
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'for': 'btn_cron_del' }),
E('div', { 'class': 'cbi-value-field' },
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 = this.onchangeHourInterval;
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) }, this.toDD(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) }, this.toDD(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, this.setCronSchedule);
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,
});
@@ -4,230 +4,238 @@
'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;
};
infoPoll: function() {
return fs.exec_direct(tools.execPath, [ 'html-info' ], 'json').catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s ]'.format(e.message, tools.execPath)
));
L.Poll.stop();
}).then(data => {
if(!data) {
return;
};
try {
data = JSON.parse(data);
} catch(err) {};
try {
data = JSON.parse(data);
} catch(err) {};
if(data.status === 'enabled') {
let date = document.getElementById('last_blacklist_update.date');
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;
};
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 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 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');
};
};
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;
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;
};
};
};
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;
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();
};
};
});
},
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)
));
})
},
load: function() {
return fs.exec_direct(tools.execPath, [ 'html-info' ], 'json').catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s ]'.format(e.message, tools.execPath)
));
})
},
render: function(data) {
if(!data) {
return;
};
render: function(data) {
if(!data) {
return;
};
try {
data = JSON.parse(data);
} catch(err) {};
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' });
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', 'style': 'min-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' }, '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' }, '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' }, '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' },
_('Last blacklist update')),
E('div', { 'class': 'td left' }, _('No data')),
])
);
};
if(data.last_blacklist_update.status) {
update_status.append(
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'style': 'min-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' }, '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' }, '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' }, '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' },
_('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', 'style': 'min-width:33%' },
_('Match-set')),
E('div', { 'class': 'th left' }, _('Bytes')),
]),
]);
if(data.iptables) {
let table_iptables = E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th left', 'style': 'min-width:33%' },
_('Match-set')),
E('div', { 'class': 'th left' }, _('Bytes')),
]),
]);
for(let [k, v] of Object.entries(data.iptables)) {
if(k === '_dummy') continue;
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',
'data-title': _('Match-set'),
}, k),
E('div', {
'class' : 'td left',
'id' : 'iptables.' + k,
'data-title': _('Bytes'),
}, v),
])
);
};
table_iptables.append(
E('div', { 'class': 'tr' }, [
E('div', {
'class': 'td left',
'data-title': _('Match-set'),
}, k),
E('div', {
'class': 'td left',
'id': 'iptables.' + k,
'data-title': _('Bytes'),
}, v),
])
);
};
iptables = E([
E('h3', {}, _('Iptables rules')),
table_iptables,
]);
};
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', 'style': 'min-width:33%' },
_('Name')),
E('div', { 'class': 'th left' },
_('Size in memory')),
E('div', { 'class': 'th left' },
_('Number of entries')),
])
);
if(data.ipset) {
let table_ipset = E('div', { 'class': 'table' },
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th left', 'style': 'min-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;
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',
'data-title': _('Name'),
}, k),
E('div', {
'class' : 'td left',
'id' : 'ipset.' + k + '.' + '0',
'data-title': _('Size in memory'),
}, v[0]),
E('div', {
'class' : 'td left',
'id' : 'ipset.' + k + '.' + '1',
'data-title': _('Number of entries'),
}, v[1]),
])
);
};
table_ipset.append(
E('div', { 'class': 'tr' }, [
E('div', {
'class': 'td left',
'data-title': _('Name'),
}, k),
E('div', {
'class': 'td left',
'id': 'ipset.' + k + '.' + '0',
'data-title': _('Size in memory'),
}, v[0]),
E('div', {
'class': 'td left',
'id': 'ipset.' + k + '.' + '1',
'data-title': _('Number of entries'),
}, v[1]),
])
);
};
ipset = E([
E('h3', {}, _('Ipset')),
table_ipset,
]);
};
ipset = E([
E('h3', {}, _('Ipset')),
table_ipset,
]);
};
L.Poll.add(this.infoPoll);
} 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)
),
]);
},
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,
handleSave : null,
handleSaveApply: null,
handleReset : null,
});
@@ -8,13 +8,13 @@ return abc.view.extend({
title: _('Ruantiblock') + ' - ' + _('Log'),
appRegexp: new RegExp(`^.*${tools.app_name}\[[0-9]+\].*$`, 'gm'),
appRegexp : new RegExp(`^.*${tools.appName}\[[0-9]+\].*$`, 'gm'),
testRegexp: new RegExp(/([0-9]{2}:){2}[0-9]{2}/),
testRegexp : new RegExp(/([0-9]{2}:){2}[0-9]{2}/),
isLoggerChecked: false,
entriesHandler: null,
entriesHandler : null,
// logd
logdHandler: function(strArray, lineNum) {
@@ -53,8 +53,9 @@ return abc.view.extend({
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(err => {
ui.addNotification(null, E('p', {}, _('Unable to load log data:') + ' ' + err.message));
return fs.exec_direct(logger, [ '-e', tools.appName ]).catch(err => {
ui.addNotification(
null, E('p', {}, _('Unable to load log data:') + ' ' + err.message));
return '';
});
};
@@ -4,342 +4,349 @@
'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();
});
});
}
const btn_style_neutral = 'btn'
const btn_style_action = 'btn cbi-button-action';
const btn_style_positive = 'btn cbi-button-save important';
const btn_style_negative = 'btn cbi-button-reset important';
const btn_style_warning = 'btn cbi-button-negative important'
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;
});
},
statusTokenValue: null,
dialog_destroy: function(ev) {
ev.target.blur();
let cancel_button = E('button', {
'class': btn_style_neutral,
'click': ui.hideModal,
}, _('Cancel'));
disableButtons: function(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");
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');
});
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;
};
},
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,
])
]);
},
getAppStatus: function() {
return Promise.all([
fs.exec(tools.execPath, [ 'raw-status' ]),
fs.exec(tools.execPath, [ 'total-proxy-status' ]),
fs.exec(tools.execPath, [ 'vpn-route-status' ]),
tools.getInitStatus(tools.appName),
L.resolveDefault(fs.read(tools.tokenFile), 0),
uci.load(tools.appName),
]).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s | %s | %s ]'.format(
e.message, tools.execPath, 'tools.getInitStatus', 'uci.ruantiblock'
)));
});
},
load: function() {
return get_app_status();
},
setAppStatus: function(status_array, elems=[], force_app_code) {
let section = uci.get(tools.appName, 'config');
if(!status_array || typeof(section) !== 'object') {
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(1);
ui.addNotification(null, E('p', _('Unable to read the contents')
+ ': setAppStatus()'));
this.disableButtons(true, null, elems);
return;
};
render: function(status_array) {
if(!status_array) {
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];
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 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;
let btn_enable = elems[2] || document.getElementById('btn_enable');
if(enabled_flag == true) {
btn_enable.onclick = ui.createHandlerFn(
this, this.serviceAction, 'disable', 'btn_enable');
btn_enable.textContent = _('Enabled');
btn_enable.className = btn_style_positive;
} else {
btn_enable.onclick = ui.createHandlerFn(
this, this.serviceAction, 'enable', 'btn_enable');
btn_enable.textContent = _('Disabled');
btn_enable.className = btn_style_negative;
};
let status_string = E('div', {
'id': 'status',
'name': 'status',
'class': 'cbi-section-node',
});
let btn_tp = elems[3] || document.getElementById('btn_tp');
if(btn_tp) {
if(tp_status_code == 0) {
btn_tp.onclick = ui.createHandlerFn(
this, this.appAction, 'total-proxy-off', 'btn_tp');
btn_tp.textContent = _('Enabled');
btn_tp.className = btn_style_positive;
} else {
btn_tp.onclick = ui.createHandlerFn(
this, this.appAction, 'total-proxy-on', 'btn_tp');
btn_tp.textContent = _('Disabled');
btn_tp.className = btn_style_negative;
};
};
let layout = E('div', { 'class': 'cbi-section-node' });
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 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 btnStartStateOn = () => {
btn_start.onclick = ui.createHandlerFn(
this, this.serviceAction, 'stop', 'btn_start');
btn_start.textContent = _('Enabled');
btn_start.className = btn_style_positive;
}
let btn_start = E('button', {
'id': 'btn_start',
'name': 'btn_start',
'class': btn_style_action,
}, _('Enable'));
layout_append(btn_start, _('Service'));
let btnStartStateOff = () => {
btn_start.onclick = ui.createHandlerFn(
this, this.serviceAction,'start', 'btn_start');
btn_start.textContent = _('Disabled');
btn_start.className = btn_style_negative;
}
let btn_enable = E('button', {
'id': 'btn_enable',
'name': 'btn_enable',
'class': btn_style_save,
}, _('Enable'));
layout_append(btn_enable, _('Run at startup'));
if(app_status_code == 0) {
this.disableButtons(false, null, elems);
btnStartStateOn();
btn_destroy.disabled = false;
btn_update.disabled = false;
if(btn_tp) {
btn_tp.disabled = false;
};
}
else if(app_status_code == 2) {
this.disableButtons(false, null, elems);
btnStartStateOff();
btn_update.disabled = true;
if(btn_tp) {
btn_tp.disabled = true;
};
}
else if(app_status_code == 3) {
btnStartStateOff();
this.disableButtons(true, btn_start, elems);
}
else if(app_status_code == 4) {
btnStartStateOn();
this.disableButtons(true, btn_update, elems);
}
else {
ui.addNotification(null, E('p', _('Error')
+ ' %s: return code = %s'.format(tools.execPath, app_status_code)));
this.disableButtons(true, null, elems);
};
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'));
};
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(
app_status_code,
proxy_mode,
bllist_mode,
bllist_module,
bllist_source,
tp_status_code,
vpn_route_status_code);
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'));
if(!L.Poll.active()) {
L.Poll.start();
};
},
let btn_destroy = E('button', {
'id': 'btn_destroy',
'name': 'btn_destroy',
'class': btn_style_reset,
}, _('Shutdown'));
btn_destroy.onclick = this.dialog_destroy;
serviceAction: function(action, button) {
if(button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
};
layout_append(btn_destroy, _('Shutdown'),
_('Complete service shutdown, as well as deleting ipsets and blacklist data'));
L.Poll.stop();
set_app_status(status_array, [
status_string,
btn_start,
btn_enable,
btn_tp,
btn_update,
btn_destroy,
]);
return tools.handleServiceAction(tools.appName, action).then(() => {
return this.getAppStatus().then(
(status_array) => {
this.setAppStatus(status_array);
}
);
});
},
L.Poll.add(this.poll_status);
appAction: function(action, button) {
if(button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
};
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
),
]);
},
L.Poll.stop();
handleSave: null,
handleSaveApply: null,
handleReset: null,
if(action === 'update') {
this.getAppStatus().then(status_array => {
this.setAppStatus(status_array, [], 4);
});
};
return fs.exec_direct(tools.execPath, [ action ]).then(res => {
return this.getAppStatus().then(
(status_array) => {
this.setAppStatus(status_array);
ui.hideModal();
}
);
});
},
statusPoll: function() {
return fs.read(tools.tokenFile).then(v => {
v = tools.normalizeValue(v);
if(v != this.statusTokenValue) {
this.getAppStatus().then(
L.bind(this.setAppStatus, this)
);
}
this.statusTokenValue = v;
}).catch(e => {
this.statusTokenValue = 0;
});
},
dialogDestroy: function(ev) {
ev.target.blur();
let cancel_button = E('button', {
'class': btn_style_neutral,
'click': ui.hideModal,
}, _('Cancel'));
let shutdown_btn = E('button', {
'class': btn_style_warning,
}, _('Shutdown'));
shutdown_btn.onclick = ui.createHandlerFn(this, () => {
cancel_button.disabled = true;
return this.appAction('destroy');
});
ui.showModal(_('Shutdown'), [
E('div', { 'class': 'cbi-section' }, [
E('p', _('The service will be disabled and all blacklist data will be deleted. Continue?')),
]),
E('div', { 'class': 'right' }, [
shutdown_btn,
' ',
cancel_button,
])
]);
},
load: function() {
return this.getAppStatus();
},
render: function(status_array) {
if(!status_array) {
return;
};
let section = uci.get(tools.appName, 'config');
let proxy_local_clients = (typeof(section) === 'object') ?
section.proxy_local_clients : null;
this.statusTokenValue = (Array.isArray(status_array)) ?
tools.normalizeValue(status_array[4]) : null;
let 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_positive,
}, _('Enable'));
layout_append(btn_enable, _('Run at startup'));
let btn_tp = E('button', {
'id' : 'btn_tp',
'name' : 'btn_tp',
'class': btn_style_positive,
}, _('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, () => { this.appAction('update', 'btn_update') });
layout_append(btn_update, _('Update blacklist'));
let btn_destroy = E('button', {
'id' : 'btn_destroy',
'name' : 'btn_destroy',
'class': btn_style_negative,
}, _('Shutdown'));
btn_destroy.onclick = L.bind(this.dialogDestroy, this);
layout_append(btn_destroy, _('Shutdown'),
_('Complete service shutdown, as well as deleting ipsets and blacklist data'));
this.setAppStatus(status_array, [
status_string,
btn_start,
btn_enable,
btn_tp,
btn_update,
btn_destroy,
]);
L.Poll.add(L.bind(this.statusPoll, this));
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,
});
@@ -6,393 +6,354 @@
'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,
availableParsers: {},
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
)));
});
},
appStatusCode : null,
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';
depends: function(elem, key, array, empty=true) {
if(empty && array.length === 0) {
elem.depends(key, '_dummy');
} else {
array.forEach(e => elem.depends(key, e));
};
},
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);
};
});
};
dependsBllistModule: function(elem) {
this.depends(elem, 'bllist_module', Object.values(this.availableParsers));
},
let m, s, o;
validateIpPort: function(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`;
},
m = new form.Map(tools.app_name, _('Ruantiblock') + ' - ' + _('Settings'));
CBIBlockTitle: form.DummyValue.extend({
string: null,
s = m.section(form.NamedSection, 'config');
s.anonymous = true;
s.addremove = false;
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)
),
]);
},
}),
/* Main settings tab */
load: function() {
return Promise.all([
L.resolveDefault(fs.exec(tools.execPath, [ 'raw-status' ]), 1),
fs.list(tools.parsersDir),
uci.load('network'),
]).catch(e => {
ui.addNotification(null, E('p', _('Unable to read the contents')
+ ': %s [ %s ]'.format(
e.message, tools.parsersDir
)));
});
},
s.tab('main_settings', _('Main settings'));
render: function(data) {
if(!data) {
return;
};
this.appStatusCode = 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';
// 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');
};
if(p_dir_arr) {
p_dir_arr.forEach(e => {
let fname = e.name;
if(fname.startsWith('ruab_parser')) {
this.availableParsers[fname] = tools.parsersDir + '/' + fname;
};
});
};
// 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;
let ip_filter_edit = new tools.fileEditDialog(
tools.ipFilterFile,
_('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>'
);
// USE_LOGGER
o = s.taboption('main_settings', form.Flag, 'use_logger',
_('Logging events'));
o.rmempty = false;
o.default = 1;
let fqdn_filter_edit = new tools.fileEditDialog(
tools.fqdnFilterFile,
_('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>'
);
// 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');
let user_entries_edit = new tools.fileEditDialog(
tools.userEntriesFile,
_('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>'
);
// 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;
let torrc_edit = new tools.fileEditDialog(
tools.torrcFile,
_('Tor configuration file'),
null,
function(rc) {
return tools.getInitStatus('tor').then(res => {
if(res) {
return tools.handleServiceAction('tor', 'restart');
};
});
}
);
let m, s, o;
m = new form.Map(tools.appName, _('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.appStatusCode == 1 || this.appStatusCode == 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;
// USE_LOGGER
o = s.taboption('main_settings', form.Flag, 'use_logger',
_('Logging events'));
o.rmempty = false;
// 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;
if(this.app_status_code == 1 || this.app_status_code == 2) {
/* Tor tab */
if(this.appStatusCode == 1 || this.appStatusCode == 2) {
/* Tor tab */
s.tab('tor_settings', _('Tor mode'));
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;
// 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';
// 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";
// 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;
// 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.validate = this.validateIpPort;
// 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';
// 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 */
/* VPN tab */
s.tab('vpn_settings', _('VPN mode'));
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;
};
// 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 */
/* Parser settings tab */
s.tab('parser_settings', _('Blacklist settings'));
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_MODULE
let bllist_module = s.taboption('parser_settings', form.ListValue,
'bllist_module', _('Blacklist module'));
bllist_module.value('', _('none (user entries only)'));
Object.entries(this.availableParsers).forEach(
e => bllist_module.value(e[1], e[0]));
// 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_MODE
let bllist_mode = s.taboption('parser_settings', form.ListValue,
'bllist_mode', _('Module operation mode'));
bllist_mode.value('ip');
bllist_mode.value('fqdn');
// 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);
// 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.blacklistSources)) {
bllist_source.value(k);
bllist_source.description += `<br />${k} - <a href="${v}" target="_blank">${v}</a>`;
};
o = s.taboption('parser_settings', CBIBlockTitle, '_dummy_ip');
o.string = _('IP configuration') + ':';
depends_bllist_module(o);
o = s.taboption('parser_settings', this.CBIBlockTitle, '_dummy_ip');
o.string = _('IP configuration') + ':';
// 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);
// 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.rmempty = false;
o.datatype = 'uinteger';
// 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 = _('e.g:') + ' <code>192.168.1.</code>';
o.placeholder = _('e.g:') + ' 192.168.1.';
o.default = '';
// 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 = _('e.g:') + ' <code>192.168.1.</code>';
o.placeholder = _('e.g:') + ' 192.168.1.';
o.validate = (section, value) => {
return (/^$|^([0-9]{1,3}[.]){3}$/.test(value)) ? true : _('Expecting:')
+ ' ' + _('net pattern') + ' (' + _('e.g:') + ' 192.168.3.)\n';
};
depends_bllist_module(o);
// SUMMARIZE_IP
o = s.taboption('parser_settings', form.Flag, 'summarize_ip',
_("Summarize IP ranges"));
o.rmempty = false;
// 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;
// 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', this.CBIBlockTitle, '_dummy_fqdn');
o.string = _('FQDN configuration') + ':';
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.rmempty = false;
o.datatype = 'uinteger';
// 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.description = _('e.g:') + ' <code>livejournal.com</code>';
o.placeholder = _('e.g:') + ' livejournal.com';
o.datatype = "hostname";
// 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;
// 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;
// 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';
// ALT_DNS_ADDR
o = s.taboption('parser_settings', form.Value, 'alt_dns_addr',
_("Optional DNS resolver"), '<code>ipaddress[#port]</code>');
o.rmempty = false;
o.validate = this.validateIpPort;
/* Entries filters tab */
/* Blacklist entry filters tab */
s.tab('entries_filter_tab', _('Entries filters'));
s.tab('entries_filter_tab', _('Blacklist entry 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
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;
// 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);
// 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';
// 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
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;
// 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);
// 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';
/* User entries tab */
/* User entries tab */
s.tab('user_entries_tab', _('User entries'));
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);
// 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;
this.dependsBllistModule(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_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 = this.validateIpPort;
// 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';
// 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;
},
let map_promise = m.render();
map_promise.then(node => node.classList.add('fade-in'));
handleSaveApply: function(ev, mode) {
return this.handleSave(ev).then(() => {
ui.changes.apply(mode == '0');
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);
};
});
},
if(this.appStatusCode != 1 && this.appStatusCode != 2) {
window.setTimeout(() => tools.handleServiceAction(
tools.appName, 'restart'), 3000);
};
});
},
});
@@ -1,252 +1,291 @@
'use strict';
'require fs';
'require rpc';
'require ui';
document.head.append(E('style', {'type': 'text/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;
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;
background-color: #a7b668 !important;
}
.running {
background-color: #2ea256 !important;
background-color: #2ea256 !important;
}
.updating {
background-color: #1e82ff !important;
background-color: #1e82ff !important;
}
.stopped {
background-color: #acacac !important;
background-color: #acacac !important;
}
.error {
background-color: #ff4e54 !important;
background-color: #ff4e54 !important;
}
.total-proxy {
background-color: #ffb937 !important;
background-color: #ffb937 !important;
}
`));
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>',
appName : 'ruantiblock',
execPath : '/usr/bin/ruantiblock',
tokenFile : '/var/run/ruantiblock.token',
parsersDir : '/usr/bin',
torrcFile : '/etc/tor/torrc',
userEntriesFile : '/etc/ruantiblock/user_entries',
fqdnFilterFile : '/etc/ruantiblock/fqdn_filter',
ipFilterFile : '/etc/ruantiblock/ip_filter',
crontabFile : '/etc/crontabs/root',
infoLabelStarting: '<span class="label-status starting">' + _('Starting') + '</span>',
infoLabelRunning : '<span class="label-status running">' + _('Enabled') + '</span>',
infoLabelUpdating: '<span class="label-status updating">' + _('Updating') + '</span>',
infoLabelStopped : '<span class="label-status stopped">' + _('Disabled') + '</span>',
infoLabelError : '<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',
},
blacklistSources: {
'rublacklist': 'https://rublacklist.net',
'zapret-info': 'https://github.com/zapret-info/z-i',
'antifilter' : 'https://antifilter.download',
},
normalize_value: function(v) {
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
},
callInitStatus: rpc.declare({
object: 'luci',
method: 'getInitList',
params: [ 'name' ],
expect: { '': {} }
}),
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 = '';
callInitAction: rpc.declare({
object: 'luci',
method: 'setInitAction',
params: [ 'name', 'action' ],
expect: { result: false }
}),
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="min-width:33%%">
${_('Status')}:
</div>
<div class="td left">
${app_status_label}
</div>
</div>
</div>`
};
getInitStatus: function(name) {
return this.callInitStatus(name).then(res => {
if(res) {
return res[name].enabled;
} else {
throw _('Command failed');
}
}).catch(e => {
ui.addNotification(null,
E('p', _('Failed to get %s init status: %s').format(name, e)));
});
},
return `<div class="table">
<div class="tr">
<div class="td left" style="min-width:33%%">
${_('Status')}:
</div>
<div class="td left%s">
%s %s %s
</div>
</div>
<div class="tr">
<div class="td left">
${_('Proxy mode')}:
</div>
<div class="td left">
%s
</div>
</div>
<div class="tr">
<div class="td left">
${_('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">
${_('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>`
);
},
handleServiceAction: function(name, action) {
return this.callInitAction(name, action).then(success => {
if(!success) {
throw _('Command failed');
};
return true;
}).catch(e => {
ui.addNotification(null,
E('p', _('Service action failed "%s %s": %s').format(name, action, e)));
});
},
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;
},
normalizeValue: function(v) {
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
},
load: function() {
return fs.read(this.file);
},
makeStatusString: 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 = '';
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')),
]),
]);
},
switch(app_status_code) {
case 0:
app_status_label = this.infoLabelRunning;
break;
case 2:
app_status_label = this.infoLabelStopped;
break;
case 3:
app_status_label = this.infoLabelStarting;
spinning = ' spinning';
break;
case 4:
app_status_label = this.infoLabelUpdating;
spinning = ' spinning';
break;
default:
app_status_label = this.infoLabelError;
return `<div class="table">
<div class="tr">
<div class="td left" style="min-width:33%%">
${_('Status')}:
</div>
<div class="td left">
${app_status_label}
</div>
</div>
</div>`
};
handleSave: function(ev) {
let textarea = document.getElementById('widget.modal_content');
let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n';
return `<div class="table">
<div class="tr">
<div class="td left" style="min-width:33%%">
${_('Status')}:
</div>
<div class="td left%s">
%s %s %s
</div>
</div>
<div class="tr">
<div class="td left">
${_('Proxy mode')}:
</div>
<div class="td left">
%s
</div>
</div>
<div class="tr">
<div class="td left">
${_('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">
${_('Blacklist source')}:
</div>
<div class="td left">
<span style="cursor:help; border-bottom:1px dotted" data-tooltip="${this.blacklistSources[bllist_source]}">
${bllist_source}
</span>
</div>
</div>`
);
},
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();
});
},
fileEditDialog: 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;
},
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'))
),
]);
};
},
load: function() {
return fs.read(this.file);
},
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);
})
},
}),
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);
})
},
}),
});