Rename all directories

This commit is contained in:
remittor
2025-12-28 20:15:29 +03:00
parent dd35b5d50a
commit c11ca1ca2e
31 changed files with 0 additions and 0 deletions

32
luci-app-zapret2/Makefile Normal file
View File

@@ -0,0 +1,32 @@
#
# Copyright (c) 2024 remittor
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-zapret2
PKG_VERSION:=0.7.20251227
PKG_RELEASE:=1
PKG_LICENSE:=MIT
PKG_MAINTAINER:=remittor <https://github.com/remittor>
LUCI_TITLE:=LuCI support for zapret2
LUCI_DEPENDS:=+zapret2
LUCI_PKGARCH:=all
define Package/$(PKG_NAME)/postinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
rm -f /tmp/luci-index*
rm -rf /tmp/luci-modulecache/
#killall -HUP rpcd 2>/dev/null
/etc/init.d/rpcd reload
[ -f "/sbin/luci-reload" ] && /sbin/luci-reload
[ -f "/etc/init.d/uhttpd" ] && /etc/init.d/uhttpd reload
fi
exit 0
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,200 @@
'use strict';
'require view';
'require fs';
'require form';
'require poll';
'require uci';
'require ui';
'require view.zapret2.tools as tools';
return view.extend({
retrieveLog: async function() {
return Promise.all([
L.resolveDefault(fs.stat('/bin/cat'), null),
fs.exec('/usr/bin/find', [ '/tmp', '-maxdepth', '1', '-type', 'f', '-name', 'zapret2+*.log' ]),
uci.load(tools.appName),
]).then(function(status_array) {
var filereader = status_array[0] ? status_array[0].path : null;
var log_data = status_array[1]; // stdout: multiline text
if (log_data.code != 0) {
ui.addNotification(null, E('p', _('Unable to get log files') + '(code = ' + log_data.code + ') : retrieveLog()'));
return null;
}
var reason = '';
var uci_cfg = uci.get(tools.appName, 'config');
if (uci_cfg !== null && typeof(uci_cfg) === 'object') {
let flag = uci_cfg.DAEMON_LOG_ENABLE;
if (flag != '1') {
reason = ' (Reason: option DAEMON_LOG_ENABLE = ' + flag + ')';
}
}
if (typeof(log_data.stdout) !== 'string') {
return 'Log files not found.' + reason;
}
var log_list = log_data.stdout.trim().split('\n');
if (log_list.length <= 0) {
return 'Log files not found!' + reason;
}
for (let i = 0; i < log_list.length; i++) {
let logfn = log_list[i].trim();
if (logfn.startsWith('/tmp/') && logfn.endsWith('+main.log')) {
log_list.splice(i, 1);
log_list.unshift(logfn);
break;
}
}
var tasks = [ ];
var logdata = [ ];
for (let i = 0; i < log_list.length; i++) {
let logfn = log_list[i].trim();
if (logfn.startsWith('/tmp/')) {
//console.log('LOG: ' + logfn);
logdata.push( { filename: logfn, data: null, rows: 0 } );
tasks.push( fs.read_direct(logfn) );
}
}
return Promise.all(tasks).then(function(log_array) {
for (let i = 0; i < log_array.length; i++) {
if (log_array[i]) {
logdata[i].data = log_array[i];
logdata[i].rows = tools.getLineCount(log_array[i]) + 1;
}
}
return logdata;
}).catch(function(e) {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s | %s | %s ]'.format(
e.message, 'retrieveLogData', 'uci.zapret'
)));
return null;
});
}).catch(function(e) {
const [, lineno, colno] = e.stack.match(/(\d+):(\d+)/);
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ lineno: %s | %s | %s | %s ]'.format(
e.message, lineno, 'retrieveLog', 'uci.zapret'
)));
return null;
});
},
pollLog: async function() {
let logdate_len = -2;
let logdata;
for (let txt_id = 0; txt_id < 10; txt_id++) {
let elem = document.getElementById('dmnlog_' + txt_id);
if (!elem)
break;
if (logdate_len == -2) {
logdata = await this.retrieveLog();
logdate_len = (Array.isArray(logdata)) ? logdata.length : -1;
}
let elem_name = elem.getAttribute("name");
let founded = false;
if (logdate_len > 0) {
for (let log_num = 0; log_num < logdate_len; log_num++) {
if (logdata[log_num].filename == elem_name) {
if (logdata[log_num].data) {
elem.value = logdata[log_num].data;
elem.rows = logdata[log_num].rows;
founded = true;
//console.log('POLL: updated ' + elem_name);
}
break;
}
}
}
if (!founded) {
elem.value = '';
elem.rows = 0;
}
}
},
load: async function() {
poll.add(this.pollLog.bind(this));
return await this.retrieveLog();
},
render: function(logdata) {
if (!logdata) {
return;
}
if (typeof(logdata) === 'string') {
return E('div', {}, [
E('p', {'class': 'cbi-title-field'}, [ logdata ]),
]);
}
if (!Array.isArray(logdata)) {
ui.addNotification(null, E('p', _('Unable to get log files') + ' : render()'));
return;
}
var h2 = E('div', {'class' : 'cbi-title-section'}, [
E('h2', {'class': 'cbi-title-field'}, [ _('Zapret2') + ' - ' + _('Log Viewer') ]),
]);
var tabs = E('div', {}, E('div'));
for (let log_num = 0; log_num < logdata.length; log_num++) {
//console.log('REN: ' + logdata[log_num].filename + ' : ' + logdata[log_num].data.length);
var logfn = logdata[log_num].filename;
let filename = logfn.replace(/.*\//, '');
let fname = filename.split('.')[0];
fname = fname.replace(/^(zapret2\+)/, '');
let fn = fname.split('+');
let tabNameText = fname.replace(/\+/g, ' ');
let tabname = 'tablog_' + log_num;
var scrollDownButton = null;
var scrollUpButton = null;
scrollDownButton = E('button', {
'id': 'scrollDownButton_' + log_num,
'class': 'cbi-button cbi-button-neutral'
}, _('Scroll to tail', 'scroll to bottom (the tail) of the log file')
);
scrollDownButton.addEventListener('click', function() {
scrollUpButton.focus();
});
scrollUpButton = E('button', {
'id' : 'scrollUpButton_' + log_num,
'class': 'cbi-button cbi-button-neutral'
}, _('Scroll to head', 'scroll to top (the head) of the log file')
);
scrollUpButton.addEventListener('click', function() {
scrollDownButton.focus();
});
let log_id = 'dmnlog_' + log_num;
let log_name = logdata[log_num].filename;
let log_text = (logdata[log_num].data) ? logdata[log_num].data : '';
let tab = E('div', { 'data-tab': tabname, 'data-tab-title': tabNameText }, [
E('div', { 'id': 'content_dmnlog_' + log_num }, [
E('div', {'style': 'margin-bottom: 20px; '}, [ scrollDownButton ]),
E('textarea', {
'id': log_id,
'name': log_name,
'style': 'font-size:12px; width: 100%; max-height: 50vh;',
'readonly': 'readonly',
'wrap': 'off',
'rows': logdata[log_num].rows,
}, [ log_text ]),
E('div', {'style': 'margin-top: 20px'}, [ scrollUpButton ]),
]),
]);
tabs.firstElementChild.appendChild(tab);
}
ui.tabs.initTabGroup(tabs.firstElementChild.childNodes);
//this.pollFn = L.bind(this.handleScanRefresh, this);
//poll.add(this.pollFn);
return E('div', { }, [ h2, tabs ]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@@ -0,0 +1,443 @@
'use strict';
'require fs';
'require poll';
'require uci';
'require ui';
'require view';
'require view.zapret2.tools as tools';
'require view.zapret2.updater as updater';
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';
const btn_style_success = 'btn cbi-button-success important';
return view.extend({
get_svc_buttons: function(elems = { }) {
return {
enable : elems.btn_enable || document.getElementById('btn_enable'),
disable : elems.btn_disable || document.getElementById('btn_disable'),
start : elems.btn_start || document.getElementById('btn_start'),
restart : elems.btn_restart || document.getElementById('btn_restart'),
stop : elems.btn_stop || document.getElementById('btn_stop'),
reset : elems.btn_reset || document.getElementById('btn_reset'),
update : elems.btn_update || document.getElementById('btn_update'),
};
},
disableButtons: function(flag, button, elems = { }) {
let error_code = 0;
if (Number.isInteger(button) && button < 0) {
error_code = button;
}
let btn = this.get_svc_buttons(elems);
btn.enable.disabled = flag;
btn.disable.disabled = flag;
btn.start.disabled = flag;
btn.restart.disabled = flag;
btn.stop.disabled = flag;
btn.reset.disabled = (error_code == 0) ? flag : false;
btn.update.disabled = (error_code == 0) ? flag : false;
},
getAppStatus: function() {
return Promise.all([
tools.getInitState(tools.appName), // svc_boot
fs.exec(tools.execPath, [ 'enabled' ]), // svc_en
tools.getSvcInfo(), // svc_info
fs.exec('/bin/busybox', [ 'ps' ]), // process list
fs.exec(tools.packager.path, tools.packager.args), // installed packages
tools.getStratList(), // nfqws strategy list
fs.exec('/bin/cat', [ '/etc/openwrt_release' ]), // CPU arch
uci.load(tools.appName), // config
]).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s | %s | %s ]'.format(
e.message, tools.execPath, 'tools.getInitState', 'uci.zapret2'
)));
});
},
setAppStatus: function(status_array, elems = { }, force_app_status = 0) {
let cfg = uci.get(tools.appName, 'config');
if (!status_array || cfg == null || typeof(cfg) !== 'object') {
let elem_status = elems.status || document.getElementById("status");
elem_status.innerHTML = tools.makeStatusString(null, '', '');
ui.addNotification(null, E('p', _('Unable to read the contents') + ': setAppStatus()'));
this.disableButtons(true, -1, elems);
return;
}
let svc_boot = status_array[0] ? true : false;
let svc_en = status_array[1]; // stdout: empty or error text
let svc_info = status_array[2]; // stdout: JSON as text
let proc_list = status_array[3]; // stdout: multiline text
let pkg_list = status_array[4]; // stdout: installed packages
let stratlist = status_array[5]; // array of strat names
let sys_info = status_array[6]; // stdout: openwrt distrib info
this.nfqws_strat_list = stratlist;
this.pkg_arch = tools.getConfigPar(sys_info.stdout, 'DISTRIB_ARCH', 'unknown');
//console.log('svc_en: ' + svc_en.code);
svc_en = (svc_en.code == 0) ? true : false;
if (typeof(svc_info) !== 'object') {
ui.addNotification(null, E('p', _('Unable to read the service info') + ': setAppStatus()'));
this.disableButtons(true, -1, elems);
return;
}
if (proc_list.code != 0) {
ui.addNotification(null, E('p', _('Unable to read process list') + ': setAppStatus()'));
this.disableButtons(true, -1, elems);
return;
}
if (pkg_list.code != 0) {
ui.addNotification(null, E('p', _('Unable to enumerate installed packages') + ': setAppStatus()'));
this.disableButtons(true, -1, elems);
return;
}
let svcinfo;
if (force_app_status) {
svcinfo = force_app_status;
} else {
svcinfo = tools.decode_svc_info(svc_en, svc_info, proc_list, cfg);
}
let btn = this.get_svc_buttons(elems);
btn.reset.disabled = false;
btn.update.disabled = false;
if (Number.isInteger(svcinfo)) {
ui.addNotification(null, E('p', _('Error')
+ ' %s: return code = %s'.format('decode_svc_info', svcinfo + ' ')));
this.disableButtons(true, -1, elems);
} else {
btn.enable.disabled = (svc_en) ? true : false;
btn.disable.disabled = (svc_en) ? false : true;
if (!svcinfo.dmn.inited) {
btn.start.disabled = false;
btn.restart.disabled = true;
btn.stop.disabled = true;
} else {
btn.start.disabled = true;
btn.restart.disabled = false;
btn.stop.disabled = false;
}
}
let elem_status = elems.status || document.getElementById("status");
elem_status.innerHTML = tools.makeStatusString(svcinfo, this.pkg_arch, '');
if (!poll.active()) {
poll.start();
}
},
serviceAction: function(action, button) {
if (button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
}
poll.stop();
let _this = this;
return tools.handleServiceAction(tools.appName, action)
.then(() => {
return _this.getAppStatus().then(
(status_array) => {
_this.setAppStatus(status_array);
}
);
})
.catch(e => {
ui.addNotification(null, E('p', _('Unable to run service action.') + ' Error: ' + e.message));
});
},
serviceActionEx: function(action, button, args = [ ], hide_modal = false) {
if (button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
}
poll.stop();
let _this = this;
let exec_cmd = null;
let exec_arg = [ ];
let errmsg = 'ERROR:';
if (action == 'start' || action == 'restart') {
exec_cmd = tools.syncCfgPath;
errmsg = _('Unable to run sync_config.sh script.');
}
else if (action == 'reset') {
exec_cmd = tools.defaultCfgPath;
exec_arg = args; // (reset_ipset)(sync) ==> restore all configs + sync config
errmsg = _('Unable to run restore-def-cfg.sh script.');
action = null;
} else {
ui.addNotification(null, E('p', 'ERROR: unknown action'));
return null;
}
return fs.exec(exec_cmd, exec_arg)
.then(function(res) {
if (res.code != 0) {
ui.addNotification(null, E('p', errmsg + ' res.code = ' + res.code));
action = null; // return with error
}
if (hide_modal) {
ui.hideModal();
}
if (!action) {
return _this.getAppStatus().then(
(status_array) => {
_this.setAppStatus(status_array);
}
);
}
return _this.serviceAction(action, null);
})
.catch(e => {
ui.addNotification(null, E('p', errmsg + ' Error: ' + e.message));
});
},
appAction: function(action, button) {
if (button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
}
poll.stop();
return fs.exec_direct(tools.execPath, [ action ]).then(res => {
return this.getAppStatus().then(
(status_array) => {
this.setAppStatus(status_array);
ui.hideModal();
}
);
});
},
statusPoll: function() {
this.getAppStatus().then(
L.bind(this.setAppStatus, this)
);
},
dialogResetCfg: function(ev) {
ev.target.blur();
let reset_base = E('label', [
E('input', { type: 'checkbox', id: 'cfg_reset_base', checked: true }),
' ', _('Restore all base settings')
]);
let reset_ipset = E('label', [
E('input', { type: 'checkbox', id: 'cfg_reset_ipset', checked: true }),
' ', _('Restore ipset configs')
]);
let set_autohostlist = E('label', [
E('input', { type: 'checkbox', id: 'cfg_autohostlist', checked: true }),
' ', _('Set AutoHostList mode')
]);
let strat_list = [ ];
strat_list.push( E('option', { value: 'strat__skip__' }, [ '-' ] ) );
for (let id = 0; id < this.nfqws_strat_list.length; id++) {
let strat = '' + this.nfqws_strat_list[id];
strat_list.push( E('option', { value: 'strat_' + id }, [ strat ] ) );
}
let nfqws_strat = E('label', [
_('NFQWS2_OPT strategy: '),
E('select', { id: 'cfg_nfqws2_strat' }, strat_list)
]);
let cancel_button = E('button', {
'class': btn_style_neutral,
'click': ui.hideModal,
}, _('Cancel'));
let resetcfg_btn = E('button', {
'class': btn_style_action,
}, _('Reset settings'));
resetcfg_btn.onclick = ui.createHandlerFn(this, () => {
//cancel_button.disabled = true;
let opt_flags = '';
if (document.getElementById('cfg_reset_base').checked == false) {
opt_flags += '(skip_base)';
};
if (document.getElementById('cfg_reset_ipset').checked) {
opt_flags += '(reset_ipset)';
};
if (document.getElementById('cfg_autohostlist').checked) {
opt_flags += '(set_mode_autohostlist)';
};
//console.log('RESET: opt_flags = ' + opt_flags);
let sel_strat = document.getElementById('cfg_nfqws2_strat');
let opt_strat = sel_strat.options[sel_strat.selectedIndex].text;
//console.log('RESET: strat = ' + opt_strat);
opt_flags += '(sync)';
let args = [ opt_flags, opt_strat ];
return this.serviceActionEx('reset', resetcfg_btn, args, true);
});
ui.showModal(_('Reset settings to default'), [
E('div', { 'class': 'cbi-section' }, [
reset_base,
E('br'), E('br'),
reset_ipset,
E('br'), E('br'),
set_autohostlist,
E('br'), E('br'),
nfqws_strat,
E('br'), E('br')
]),
E('div', { 'class': 'right' }, [
cancel_button,
' ',
resetcfg_btn,
])
]);
},
load: function() {
var _this = this;
return Promise.all([
L.resolveDefault(fs.stat('/bin/cat'), null),
]).then(function(data) {
return _this.getAppStatus();
});
},
render: function(status_array) {
if (!status_array) {
return;
}
let cfg = uci.get(tools.appName, 'config');
let pkg_list = status_array[4];
if (pkg_list === undefined || typeof(pkg_list) !== 'object' || pkg_list.code != 0) {
ui.addNotification(null, E('p', _('Unable to enumerate installed packages') + ': setAppStatus()'));
return;
}
let status_string = E('div', {
'id' : 'status',
'name' : 'status',
'class': 'cbi-section-node',
});
let layout = E('div', { 'class': 'cbi-section-node' });
function layout_append(title, descr, elems) {
descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : '';
let elist = elems;
let elem_list = [ ];
for (let i = 0; i < elist.length; i++) {
elem_list.push(elist[i]);
elem_list.push(' ');
}
let vlist = [ E('div', {}, elem_list ) ];
for (let i = 0; i < elist.length; i++) {
let input = E('input', {
'id' : elist[i].id + '_hidden',
'type': 'hidden',
});
vlist.push(input);
}
let elem_name = (elist.length == 1) ? elist[0].id + '_hidden' : null;
layout.append(
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'for': elem_name }, title),
E('div', { 'class': 'cbi-value-field' }, vlist),
])
);
}
let create_btn = function(name, _class, locname) {
return E('button', {
'id' : name,
'name' : name,
'class': _class,
}, locname);
};
let btn_enable = create_btn('btn_enable', btn_style_success, _('Enable'));
btn_enable.onclick = ui.createHandlerFn(this, this.serviceAction, 'enable', 'btn_enable');
let btn_disable = create_btn('btn_disable', btn_style_warning, _('Disable'));
btn_disable.onclick = ui.createHandlerFn(this, this.serviceAction, 'disable', 'btn_disable');
layout_append(_('Service autorun control'), null, [ btn_enable, btn_disable ] );
let btn_start = create_btn('btn_start', btn_style_action, _('Start'));
btn_start.onclick = ui.createHandlerFn(this, this.serviceActionEx, 'start', 'btn_start');
let btn_restart = create_btn('btn_restart', btn_style_action, _('Restart'));
btn_restart.onclick = ui.createHandlerFn(this, this.serviceActionEx, 'restart', 'btn_restart');
let btn_stop = create_btn('btn_stop', btn_style_warning, _('Stop'));
btn_stop.onclick = ui.createHandlerFn(this, this.serviceAction, 'stop', 'btn_stop');
layout_append(_('Service daemons control'), null, [ btn_start, btn_restart, btn_stop ] );
let btn_reset = create_btn('btn_reset', btn_style_action, _('Reset settings'));
btn_reset.onclick = L.bind(this.dialogResetCfg, this);
layout_append(_('Reset settings to default'), null, [ btn_reset ] );
let btn_update = create_btn('btn_update', btn_style_action, _('Update'));
btn_update.onclick = ui.createHandlerFn(this, () => { updater.openUpdateDialog(this.pkg_arch) });
layout_append(_('Update package'), null, [ btn_update ] );
let elems = {
"status": status_string,
"btn_enable": btn_enable,
"btn_disable": btn_disable,
"btn_start": btn_start,
"btn_restart": btn_restart,
"btn_stop": btn_stop,
"btn_reset": btn_reset,
"btn_update": btn_update,
};
this.setAppStatus(status_array, elems);
poll.add(L.bind(this.statusPoll, this));
let page_title = _('Zapret2');
let pkgdict = tools.decode_pkg_list(pkg_list.stdout, false);
page_title += ' &nbsp ';
if (pkgdict['zapret2'] === undefined || pkgdict['zapret2'] == '') {
page_title += 'unknown version';
} else {
page_title += 'v' + pkgdict['zapret2'];
}
let aux1 = E('em');
let aux2 = E('em');
if (pkgdict['zapret2'] != pkgdict['luci-app-zapret2']) {
let errtxt = 'LuCI APP v' + pkgdict['luci-app-zapret2'] + ' [ incorrect version! ]';
aux1 = E('div', { 'class': 'label-status error' }, errtxt);
aux2 = E('div', { }, '&nbsp');
}
let url1 = 'https://github.com/bol-van/zapret2';
let url2 = 'https://github.com/remittor/zapret-openwrt';
return E([
E('h2', { 'class': 'fade-in' }, page_title),
aux1,
aux2,
E('div', { 'class': 'cbi-section-descr fade-in' },
E('a', { 'href': url1, 'target': '_blank' }, url1),
),
E('div', { 'class': 'cbi-section-descr fade-in' },
E('a', { 'href': url2, 'target': '_blank' }, url2),
),
E('div', { 'class': 'cbi-section fade-in' }, [
status_string,
]),
E('div', { 'class': 'cbi-section fade-in' },
layout
),
]);
},
handleSave : null,
handleSaveApply: null,
handleReset : null,
});

View File

@@ -0,0 +1,432 @@
'use strict';
'require fs';
'require form';
'require tools.widgets as widgets';
'require uci';
'require ui';
'require view';
'require view.zapret2.tools as tools';
return view.extend({
parsers: { },
appStatusCode: null,
depends: function(elem, key, array, empty=true) {
if (empty && array.length === 0) {
elem.depends(key, '_dummy');
} else {
array.forEach(e => elem.depends(key, e));
}
},
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`;
},
validateUrl: function(section, value) {
return (/^$|^https?:\/\/[\w.-]+(:[0-9]{2,5})?[\w\/~.&?+=-]*$/.test(value)) ? true : _('Expecting:')
+ ` ${_('valid URL')}\n`;
},
load: function() {
return Promise.all([
{ code: -1}, // L.resolveDefault(fs.exec(tools.execPath, [ 'raw-status' ]), 1),
null, // L.resolveDefault(fs.list(tools.parsersDir), null),
uci.load(tools.appName),
]).catch(e => {
ui.addNotification(null, E('p', _('Unable to read the contents') + ': %s '.format(e.message) ));
});
},
render: function(data) {
if (!data) {
return;
}
this.appStatusCode = data[0].code;
let m, s, o, tabname;
m = new form.Map(tools.appName, _('Zapret2') + ' - ' + _('Settings'));
s = m.section(form.NamedSection, 'config');
s.anonymous = true;
s.addremove = false;
/* Main settings tab */
tabname = 'main_settings';
s.tab(tabname, _('Main settings'));
o = s.taboption(tabname, form.ListValue, 'FWTYPE', _('FWTYPE'));
o.value('nftables', 'nftables');
//o.value('iptables', 'iptables');
//o.value('ipfw', 'ipfw');
o = s.taboption(tabname, form.Flag, 'POSTNAT', _('POSTNAT'));
o.rmempty = false;
o.default = 1;
o = s.taboption(tabname, form.ListValue, 'FLOWOFFLOAD', _('FLOWOFFLOAD'));
o.value('donttouch', 'donttouch');
o.value('none', 'none');
o.value('software', 'software');
o.value('hardware', 'hardware');
o = s.taboption(tabname, form.Flag, 'INIT_APPLY_FW', _('INIT_APPLY_FW'));
o.rmempty = false;
o.default = 0;
o = s.taboption(tabname, form.Flag, 'DISABLE_IPV4', _('DISABLE_IPV4'));
o.rmempty = false;
o.default = 1;
o = s.taboption(tabname, form.Flag, 'DISABLE_IPV6', _('DISABLE_IPV6'));
o.rmempty = false;
o.default = 0;
o = s.taboption(tabname, form.Flag, 'FILTER_TTL_EXPIRED_ICMP', 'FILTER_TTL_EXPIRED_ICMP');
o.rmempty = false;
o.default = 1;
//o = s.taboption(tabname, form.ListValue, 'MODE_FILTER', _('MODE_FILTER'));
//o.value('none', 'none');
//o.value('ipset', 'ipset');
//o.value('hostlist', 'hostlist');
//o.value('autohostlist', 'autohostlist');
o = s.taboption(tabname, form.Value, 'WS_USER', _('WS_USER'));
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Flag, 'DAEMON_LOG_ENABLE', _('DAEMON_LOG_ENABLE'));
o.rmempty = false;
o.default = 0;
/* NFQWS_OPT_DESYNC tab */
tabname = 'nfqws2_params';
s.tab(tabname, _('NFQWS2 options'));
let add_delim = function(sec, url = null) {
let o = sec.taboption(tabname, form.DummyValue, '_hr');
o.rawhtml = true;
o.default = '<hr style="width: 620px; height: 1px; margin: 1px 0 1px; border-top: 1px solid;">';
if (url) {
o.default += '<br/>' + _('Help') + ': <a target=_blank href=%s>%s</a>'.format(url);
}
};
let add_param = function(sec, param, locname = null, rows = 10, multiline = false) {
if (!locname)
locname = param;
let btn = sec.taboption(tabname, form.Button, '_' + param + '_btn', locname);
btn.inputtitle = _('Edit');
btn.inputstyle = 'edit btn';
let val = sec.taboption(tabname, form.DummyValue, '_' + param);
val.rawhtml = multiline ? true : false;
val.cfgvalue = function(section_id) {
let value = uci.get(tools.appName, section_id, param);
if (value == null) {
return "";
}
value = value.trim();
if (multiline == 2) {
value = value.replace(/\n --/g, "\n--");
value = value.replace(/\n --/g, "\n--");
value = value.replace(/ --/g, "\n--");
}
if (val.rawhtml) {
value = value.replace(/</g, '˂');
value = value.replace(/>/g, '˃');
value = value.replace(/\n/g, '<br/>');
}
return value;
};
val.validate = function(section_id, value) {
return (value) ? value.trim() : "";
};
let desc = locname;
if (multiline == 2) {
desc += '<br/>' + _('Example') + ': <a target=_blank href=%s>%s</a>'.format(tools.nfqws_opt_url);
}
btn.onclick = () => new tools.longstrEditDialog('config', param, param, desc, rows, multiline).show();
};
o = s.taboption(tabname, form.Flag, 'NFQWS2_ENABLE', _('NFQWS2_ENABLE'));
o.rmempty = false;
o.default = 1;
o = s.taboption(tabname, form.Value, 'DESYNC_MARK', _('DESYNC_MARK'));
//o.description = _("nfqws option for DPI desync attack");
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'DESYNC_MARK_POSTNAT', _('DESYNC_MARK_POSTNAT'));
//o.description = _("nfqws option for DPI desync attack");
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'FILTER_MARK', _('FILTER_MARK'));
o.rmempty = false;
o.validate = function(section_id, value) { return true; };
o.write = function(section_id, value) { return form.Value.prototype.write.call(this, section_id, (value == null || value.trim() == '') ? "\t" : value.trim()); };
o = s.taboption(tabname, form.Value, 'NFQWS2_PORTS_TCP', _('NFQWS2_PORTS_TCP'));
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'NFQWS2_PORTS_UDP', _('NFQWS2_PORTS_UDP'));
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'NFQWS2_TCP_PKT_OUT', _('NFQWS2_TCP_PKT_OUT'));
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'NFQWS2_TCP_PKT_IN', _('NFQWS2_TCP_PKT_IN'));
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'NFQWS2_UDP_PKT_OUT', _('NFQWS2_UDP_PKT_OUT'));
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'NFQWS2_UDP_PKT_IN', _('NFQWS2_UDP_PKT_IN'));
o.rmempty = false;
o.datatype = 'string';
o = s.taboption(tabname, form.Value, 'NFQWS2_PORTS_TCP_KEEPALIVE', _('NFQWS2_PORTS_TCP_KEEPALIVE'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Value, 'NFQWS2_PORTS_UDP_KEEPALIVE', _('NFQWS2_PORTS_UDP_KEEPALIVE'));
o.rmempty = false;
o.datatype = 'uinteger';
add_delim(s, tools.nfqws_opt_url);
add_param(s, 'NFQWS2_OPT', null, 21, 2);
/* AutoHostList settings */
tabname = 'autohostlist_tab';
s.tab(tabname, _('AutoHostList'));
o = s.taboption(tabname, form.Flag, 'MODE_FILTER', _('Use AutoHostList mode'));
o.rmempty = false;
o.default = '0';
o.validate = function(section_id, value) { return true; };
o.load = function(section_id) {
return uci.load(tools.appName).then(L.bind(function() {
var v = uci.get(tools.appName, section_id, 'MODE_FILTER');
return (v === 'autohostlist') ? '1' : '0';
}, this));
};
o.write = function(section_id, value) {
return uci.set(tools.appName, section_id, 'MODE_FILTER', value === '1' ? 'autohostlist' : 'hostlist');
};
o = s.taboption(tabname, form.Value, 'AUTOHOSTLIST_INCOMING_MAXSEQ', _('INCOMING_MAXSEQ'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Value, 'AUTOHOSTLIST_RETRANS_MAXSEQ', _('RETRANS_MAXSEQ'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Value, 'AUTOHOSTLIST_RETRANS_THRESHOLD', _('RETRANS_THRESHOLD'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Value, 'AUTOHOSTLIST_FAIL_THRESHOLD', _('FAIL_THRESHOLD'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Value, 'AUTOHOSTLIST_FAIL_TIME', _('FAIL_TIME'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Value, 'AUTOHOSTLIST_UDP_IN', _('UDP_IN'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Value, 'AUTOHOSTLIST_UDP_OUT', _('UDP_OUT'));
o.rmempty = false;
o.datatype = 'uinteger';
o = s.taboption(tabname, form.Button, '_auto_host_btn', _('Auto host list entries'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.autoHostListFN;
o.onclick = () => new tools.fileEditDialog(
tools.autoHostListFN,
_('Auto host list'),
'',
'',
15
).show();
o = s.taboption(tabname, form.Flag, 'AUTOHOSTLIST_DEBUGLOG', _('DEBUGLOG'));
o.rmempty = false;
o.default = 0;
o = s.taboption(tabname, form.Button, '_auto_host_debug_btn', _('Auto host debug list entries'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.autoHostListDbgFN;
o.onclick = () => new tools.fileEditDialog(
tools.autoHostListDbgFN,
_('Auto host debug list'),
'',
'',
15
).show();
/* HostList settings */
tabname = 'hostlist_tab';
s.tab(tabname, _('Host lists'));
o = s.taboption(tabname, form.Button, '_google_entries_btn', _('Google hostname entries'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.hostsGoogleFN;
o.onclick = () => new tools.fileEditDialog(
tools.hostsGoogleFN,
_('Google hostname entries'),
_('One hostname per line.<br />Examples:'),
'<code>youtube.com<br />googlevideo.com</code>',
15
).show();
o = s.taboption(tabname, form.Button, '_user_entries_btn', _('User hostname entries <HOSTLIST>'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.hostsUserFN;
o.onclick = () => new tools.fileEditDialog(
tools.hostsUserFN,
_('User entries'),
_('One hostname per line.<br />Examples:'),
'<code>domain.net<br />sub.domain.com<br />facebook.com</code>',
15
).show();
o = s.taboption(tabname, form.Button, '_user_excluded_entries_btn', _('User excluded hostname entries'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.hostsUserExcludeFN;
o.onclick = () => new tools.fileEditDialog(
tools.hostsUserExcludeFN,
_('User excluded entries'),
_('One hostname per line.<br />Examples:'),
'<code>domain.net<br />sub.domain.com<br />gosuslugi.ru</code>',
15
).show();
add_delim(s);
o = s.taboption(tabname, form.Button, '_ip_exclude_filter_btn', _('Excluded IP entries'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.iplstExcludeFN;
o.onclick = () => new tools.fileEditDialog(
tools.iplstExcludeFN,
_('Excluded IP filter'),
_('Patterns can be strings or regular expressions. Each pattern in a separate line<br />Examples:'),
'<code>128.199.0.0/16<br />34.217.90.52<br />162.13.190.77</code>',
15
).show();
o = s.taboption(tabname, form.Button, '_user_ip_filter_btn', _('User IP entries'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.iplstUserFN;
o.onclick = () => new tools.fileEditDialog(
tools.iplstUserFN,
_('User IP filter'),
_('Patterns can be strings or regular expressions. Each pattern in a separate line<br />Examples:'),
'<code>128.199.0.0/16<br />34.217.90.52<br />162.13.190.77</code>',
15
).show();
o = s.taboption(tabname, form.Button, '_user_excluded_ip_filter_btn', _('User excluded IP entries'));
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = tools.iplstUserExcludeFN;
o.onclick = () => new tools.fileEditDialog(
tools.iplstUserExcludeFN,
_('User excluded IP filter'),
_('Patterns can be strings or regular expressions. Each pattern in a separate line<br />Examples:'),
'<code>128.199.0.0/16<br />34.217.90.52<br />162.13.190.77</code>',
15
).show();
add_delim(s);
for (let num = 1; num <= tools.custFileMax; num++) {
let fn = tools.custFileTemplate.format(num.toString());
let name = _('Custom file #' + num);
o = s.taboption(tabname, form.Button, '_cust_file%d_btn'.format(num), name);
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = fn;
o.onclick = () => new tools.fileEditDialog(fn, name, '', '', 15).show();
}
/* custom.d files */
tabname = 'custom_d_tab';
s.tab(tabname, 'custom.d');
o = s.taboption(tabname, form.Flag, 'DISABLE_CUSTOM', _('Use custom.d scripts'));
o.rmempty = false;
o.default = '0';
o.validate = function(section_id, value) { return true; };
o.load = function(section_id) {
return uci.load(tools.appName).then(L.bind(function() {
var v = uci.get(tools.appName, section_id, 'DISABLE_CUSTOM');
return (v === '1') ? '0' : '1';
}, this));
};
o.write = function(section_id, value) {
return uci.set(tools.appName, section_id, 'DISABLE_CUSTOM', value === '1' ? '0' : '1');
};
add_delim(s);
for (let i = 0; i < tools.customdPrefixList.length; i++) {
let num = tools.customdPrefixList[i];
let fn = tools.customdFileFormat.format(num.toString());
let name = _('custom.d script #' + num);
o = s.taboption(tabname, form.Button, '_customd_file%d_btn'.format(num), name);
o.inputtitle = _('Edit');
o.inputstyle = 'edit btn';
o.description = fn;
let desc = '';
if (num == tools.discord_num) {
desc = _('Example') + ': ';
for (let k = 0; k < tools.discord_url.length; k++) {
let url = tools.discord_url[k];
if (k > 0) desc += ' <br> ';
const filename = url.substring(url.lastIndexOf("/") + 1).split("?")[0];
desc += '<a target=_blank href=' + url + '>' + filename + '</a>';
}
}
o.onclick = () => new tools.fileEditDialog(fn, name, desc, '', 15).show();
}
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.appStatusCode != 1 && this.appStatusCode != 2) {
// window.setTimeout(() => fs.exec(tools.execPath, [ 'restart' ]), 3000);
//}
});
},
});

View File

@@ -0,0 +1,605 @@
'use strict';
'require baseclass';
'require fs';
'require rpc';
'require ui';
'require uci';
document.head.append(E('style', {'type': 'text/css'},
`
.label-status {
display: inline;
margin: 0 2px 0 0 !important;
padding: 2px 4px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
font-weight: bold;
color: #fff !important;
}
.starting {
background-color: #9c994c !important;
}
.running {
background-color: #2ea256 !important;
}
.updating {
background-color: #1e82ff !important;
}
.stopped {
background-color: #8a8a8a !important;
}
.error {
background-color: #ff4e54 !important;
}
`));
return baseclass.extend({
packager : null,
appName : 'zapret2',
execPath : '/etc/init.d/zapret2',
syncCfgPath : '/opt/zapret2/sync_config.sh',
defCfgPath : '/opt/zapret2/def-cfg.sh',
defaultCfgPath : '/opt/zapret2/restore-def-cfg.sh',
hostsGoogleFN : '/opt/zapret2/ipset/zapret-hosts-google.txt',
hostsUserFN : '/opt/zapret2/ipset/zapret-hosts-user.txt',
hostsUserExcludeFN: '/opt/zapret2/ipset/zapret-hosts-user-exclude.txt',
iplstExcludeFN : '/opt/zapret2/ipset/zapret-ip-exclude.txt',
iplstUserFN : '/opt/zapret2/ipset/zapret-ip-user.txt',
iplstUserExcludeFN: '/opt/zapret2/ipset/zapret-ip-user-exclude.txt',
custFileMax : 4,
custFileTemplate : '/opt/zapret2/ipset/cust%s.txt',
customdPrefixList : [ 10, 20, 50, 60, 90 ] ,
customdFileFormat : '/opt/zapret2/init.d/openwrt/custom.d/%s-script.sh',
discord_num : 50,
discord_url : [ 'https://github.com/bol-van/zapret2/blob/master/init.d/custom.d.examples.linux/50-discord-media',
'https://github.com/bol-van/zapret2/blob/master/init.d/custom.d.examples.linux/50-stun4all',
'https://github.com/bol-van/zapret2/tree/master/init.d/custom.d.examples.linux'
],
nfqws_opt_url : 'https://github.com/remittor/zapret-openwrt/discussions/',
autoHostListFN : '/opt/zapret2/ipset/zapret-hosts-auto.txt',
autoHostListDbgFN : '/opt/zapret2/ipset/zapret-hosts-auto-debug.log',
infoLabelRunning : '<span class="label-status running">' + _('Running') + '</span>',
infoLabelStarting : '<span class="label-status starting">' + _('Starting') + '</span>',
infoLabelStopped : '<span class="label-status stopped">' + _('Stopped') + '</span>',
infoLabelDisabled : '<span class="label-status stopped">' + _('Disabled') + '</span>',
infoLabelError : '<span class="label-status error">' + _('Error') + '</span>',
infoLabelUpdating : '<span class="label-status updating">' + _('Updating') + '</span>',
statusDict: {
error : { code: 0, name: _('Error') , label: this.infoLabelError },
disabled : { code: 1, name: _('Disabled') , label: this.infoLabelDisabled },
stopped : { code: 2, name: _('Stopped') , label: this.infoLabelStopped },
starting : { code: 3, name: _('Starting') , label: this.infoLabelStarting },
running : { code: 4, name: _('Running') , label: this.infoLabelRunning },
},
callServiceList: rpc.declare({
object: 'service',
method: 'list',
params: [ 'name', 'verbose' ],
expect: { '': {} }
}),
callInitState: rpc.declare({
object: 'luci',
method: 'getInitList',
params: [ 'name' ],
expect: { '': {} }
}),
callInitAction: rpc.declare({
object: 'luci',
method: 'setInitAction',
params: [ 'name', 'action' ],
expect: { result: false }
}),
init_consts: function() {
if (!this.packager) {
this.packager = { };
if (L.hasSystemFeature('apk')) {
this.packager.name = 'apk';
this.packager.path = '/usr/bin/apk';
this.packager.args = [ 'list', '-I', '*zapret2*' ];
} else {
this.packager.name = 'opkg';
this.packager.path = '/bin/opkg';
this.packager.args = [ 'list-installed', '*zapret2*' ];
}
//console.log('PACKAGER: ' + this.packager.name);
}
},
getSvcInfo: function(svc_name = null) {
this.init_consts();
let name = (svc_name) ? svc_name : this.appName;
let verbose = 1;
return this.callServiceList(name, verbose).then(res => {
return res;
}).catch(e => {
ui.addNotification(null, E('p', _('Failed to get %s service info: %s').format(name, e)));
});
},
getInitState: function(name) {
this.init_consts();
return this.callInitState(name).then(res => {
if (res) {
return res[name].enabled ? true : false;
} else {
throw _('Command failed');
}
}).catch(e => {
ui.addNotification(null, E('p', _('Failed to get %s init status: %s').format(name, e)));
});
},
getStratList: function() {
this.init_consts();
let exec_cmd = '/bin/busybox';
let exec_arg = [ 'awk', '-F', '"', '/if \\[ "\\$strat" = "/ {print $4}', this.defCfgPath ];
return fs.exec(exec_cmd, exec_arg).then(res => {
if (res.code == 0) {
return this.getWordsArray(res.stdout);
}
return [ ];
}).catch(e => {
ui.addNotification(null, E('p', _('Failed to get strat list: %s').format(e)));
});
},
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)));
});
},
normalizeValue: function(v) {
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
},
getWordsArray: function (text, { trim = true, removeEmpty = true } = {}) {
const rawLines = text.split(/\n/);
const processed = trim ? rawLines.map(line => line.trim()) : rawLines.slice();
return removeEmpty ? processed.filter(line => line.length > 0) : processed;
},
getConfigPar: function(txt, key, defval = null) {
const re = new RegExp(`^${key}\\s*=\\s*(['"])(.*?)\\1`, 'm');
const m = txt.match(re);
return m ? m[2] : defval;
},
decode_pkg_list: function(pkg_list, with_suffix_r1 = true) {
let pkg_dict = { };
if (!pkg_list) {
return pkg_dict;
}
let lines = pkg_list.trim().split('\n');
for (let i = 0; i < lines.length; i++) {
let line = lines[i].trim();
let name;
let ver;
let rev = -1;
if (this.packager.name == 'apk') {
let fullname = line.split(' ')[0];
let match = fullname.match(/^(.*)-r(\d+)$/);
if (match) {
fullname = match[1];
rev = parseInt(match[2], 10);
}
let mpos = fullname.lastIndexOf('-');
if (mpos <= 0)
continue; // incorrect format
name = fullname.slice(0, mpos).trim();
ver = fullname.slice(mpos + 1).trim();
} else {
if (!line.includes(' - '))
continue; // incorrect format
name = line.split(' - ')[0].trim();
ver = line.split(' - ')[1].trim();
let spos = ver.indexOf(" ");
if (spos > 0) {
ver = ver.substring(0, spos);
}
let match = ver.match(/^(.*)-r(\d+)$/);
if (match) {
ver = match[1];
rev = parseInt(match[2], 10);
}
}
if (rev >= 0) {
if (rev == 1 && !with_suffix_r1) {
// nothing
} else {
ver += '-r' + rev;
}
}
pkg_dict[name] = ver;
}
return pkg_dict;
},
get_pid_list: function(proc_list) {
let plist = [ ];
let lines = proc_list.trim().split('\n');
for (let i = 0; i < lines.length; i++) {
let line = lines[i].trim();
if (line.length > 5) {
let word_list = line.split(/\s+/);
let pid = word_list[0];
let isnum = /^\d+$/.test(pid);
if (isnum) {
plist.push(parseInt(pid));
}
}
}
return plist;
},
decode_svc_info: function(svc_autorun, svc_info, proc_list, cfg) {
let result = {
"autorun": svc_autorun,
"dmn": {
inited: false,
total: 0,
running: 0,
working: 0,
},
"status": this.statusDict.error,
};
if (proc_list.code != 0) {
return -2;
}
let plist = this.get_pid_list(proc_list.stdout);
if (plist.length < 4) {
return -3;
}
if (typeof(svc_info) !== 'object') {
return -4;
}
let jdata = svc_info;
if (typeof(jdata.zapret2) == 'object') {
result.dmn.inited = true;
let dmn_list = jdata.zapret2.instances;
if (typeof(dmn_list) == 'object') {
for (const [dmn_name, daemon] of Object.entries(dmn_list)) {
result.dmn.total += 1;
if (daemon.running) {
result.dmn.running += 1;
}
if (daemon.pid !== undefined && daemon.pid != null) {
if (plist.includes(daemon.pid)) {
result.dmn.working += 1;
}
}
}
}
}
//console.log('SVC_DAEMONS: ' + result.dmn.working + ' / ' + result.dmn.total);
if (result.dmn.total == 0) {
result.status = (!svc_autorun) ? this.statusDict.disabled : this.statusDict.stopped;
} else {
result.status = (result.dmn.inited) ? this.statusDict.started : this.statusDict.running;
}
return result;
},
makeStatusString: function(svcinfo, pkg_arch, bllist_preset) {
let svc_autorun = _('Unknown');
let svc_daemons = _('Unknown');
if (typeof(svcinfo) == 'object') {
svc_autorun = (svcinfo.autorun) ? _('Enabled') : _('Disabled');
if (!svcinfo.dmn.inited) {
svc_daemons = _('Stopped');
} else {
svc_daemons = (!svcinfo.dmn.working) ? _('Starting') : _('Running');
svc_daemons += ' [' + svcinfo.dmn.working + '/' + svcinfo.dmn.total + ']';
}
}
let td_name_width = 40;
let td_name_style = `style="width: ${td_name_width}%; min-width:${td_name_width}%; max-width:${td_name_width}%;"`;
let out = `
<table class="table">
<tr class="tr">
<td class="td left" ${td_name_style}>
${_('CPU architecture')}:
</td>
<td class="td left">
${pkg_arch}
</td>
</tr>
<tr class="tr">
<td class="td left" ${td_name_style}>
${_('Service autorun status')}:
</td>
<td class="td left">
${svc_autorun}
</td>
</tr>
<tr class="tr">
<td class="td left" ${td_name_style}>
${_('Service daemons status')}:
</td>
<td class="td left %s">
${svc_daemons}
</td>
</tr>
<tr class="tr">
<td class="td left" ${td_name_style}>
</td>
<td class="td left">
</td>
</tr>
</table>`;
return out;
},
getLineCount: function(mstr) {
let count = 0;
let c = '\n'.charAt(0);
for (let i = 0; i < mstr.length; ++i) {
if (c === mstr.charAt(i)) {
++count;
}
}
return count;
},
fileEditDialog: baseclass.extend({
__init__: function(file, title, desc, aux = null, rows = 10, callback, file_exists = false) {
this.file = file;
this.title = title;
this.desc = desc;
this.aux = aux;
this.rows = rows,
this.callback = callback;
this.file_exists = file_exists;
},
load: function() {
return L.resolveDefault(fs.read(this.file), '');
},
render: function(content) {
let descr = this.desc;
if (this.aux)
descr += '<br />' + this.aux;
ui.showModal(this.title, [
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'cbi-section-descr' }, descr),
E('div', { 'class': 'cbi-section' },
E('p', {},
E('textarea', {
'id': 'widget.modal_content',
'class': 'cbi-input-textarea',
'style': 'width:100% !important',
'rows': this.rows,
'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.handleSaveAdv),
}, _('Save')),
]),
]);
},
handleSaveAdv: function(ev) {
let txt = document.getElementById('widget.modal_content');
let value = txt.value.trim().replace(/\r\n/g, '\n') + '\n';
return fs.write(this.file, value).then(async rc => {
txt.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);
});
},
}),
longstrEditDialog: baseclass.extend({
__init__: function(cfgsec, cfgparam, title, desc, rows = 10, multiline = false) {
this.cfgsec = cfgsec;
this.cfgparam = cfgparam;
this.title = title;
this.desc = desc;
this.rows = rows;
this.multiline = multiline;
},
load: function() {
let value = uci.get('zapret2', this.cfgsec, this.cfgparam);
if (typeof(value) === 'string') {
value = value.trim();
if (this.multiline == 2) {
value = value.replace(/\n\t/g, "\n");
value = value.replace(/\n\t/g, "\n");
value = value.replace(/\n\t/g, "\n");
value = value.replace(/\n\t/g, "\n");
value = value.replace(/\n\t/g, "\n");
value = value.replace(/\n\t/g, "\n");
value = value.replace(/\n --/g, "\n--");
value = value.replace(/\n --/g, "\n--");
value = value.replace(/ --/g, "\n--");
}
}
return value;
},
render: function(content) {
ui.showModal(this.title, [
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'cbi-section-descr' }, this.desc),
E('div', { 'class': 'cbi-section' },
E('p', {},
E('textarea', {
'id': 'widget.modal_content',
'class': 'cbi-input-textarea',
'style': 'width:100% !important',
'rows': this.rows,
'wrap': 'on',
'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.handleSaveAdv),
}, _('Save')),
]),
]);
},
handleSaveAdv: function(ev) {
let txt = document.getElementById('widget.modal_content');
let value = txt.value.trim();
if (this.multiline) {
value = value.replace(/\r/g, '');
if (value != "" && value != "\t") {
value = '\n' + value + '\n';
if (this.multiline == 2) {
value = value.replace(/"/g, '');
value = value.replace(/'/g, '');
}
}
} else {
value = value.replace(/\r\n/g, ' ');
value = value.replace(/\r/g, ' ');
value = value.replace(/\n/g, ' ');
value = value.trim();
}
if (value == "") {
value = "\t";
}
value = value.replace(/˂/g, '<');
value = value.replace(/˃/g, '>');
try {
let elem2 = null;
let elem = document.getElementById("cbi-zapret-" + this.cfgsec + "-_" + this.cfgparam);
if (elem) {
if (!elem2) {
elem2 = elem.querySelector('div');
}
if (!elem2) {
elem2 = elem.querySelector('output');
}
}
if (elem2) {
let val = value.trim();
if (this.multiline) {
val = val.replace(/</g, '˂');
val = val.replace(/>/g, '˃');
val = val.replace(/\n/g, '<br/>');
elem2.innerHTML = val;
} else {
elem2.textContent = val;
}
}
} catch(e) {
console.error('ERROR: cannot found elem for ' + this.cfgparam);
}
uci.set('zapret2', this.cfgsec, this.cfgparam, value);
uci.save().then(ui.hideModal);
},
error: function(e) {
let msg = (typeof(e) == 'object') ? e.message : ''+e;
ui.showModal(this.title, [
E('div', { 'class': 'cbi-section' },
E('p', {}, _('Unable to read the contents') + ': %s'.format(msg))
),
E('div', { 'class': 'right' },
E('button', {
'class': 'btn',
'click': ui.hideModal,
}, _('Dismiss'))
),
]);
},
show: function() {
ui.showModal(null,
E('p', { 'class': 'spinning' }, _('Loading'))
);
L.resolveDefault(this.load(), null)
.then(content => {
ui.hideModal();
return this.render(content);
}).catch(e => {
ui.hideModal();
return this.error(e);
})
},
}),
});

View File

@@ -0,0 +1,245 @@
'use strict';
'require baseclass';
'require fs';
'require poll';
'require uci';
'require ui';
'require view';
'require view.zapret2.tools as tools';
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';
const btn_style_success = 'btn cbi-button-success important';
const fn_update_pkg_sh = '/opt/zapret2/update-pkg.sh';
return baseclass.extend({
releasesUrlPrefix : 'https://raw.githubusercontent.com/remittor/zapret-openwrt/gh-pages/releases/',
appendLog: function(msg, end = '\n') {
this.logArea.value += msg + end;
this.logArea.scrollTop = this.logArea.scrollHeight;
},
setBtnMode: function(enable) {
this.btn_cancel.disabled = enable ? false : true;
this.btn_action.disabled = (enable == 2) ? false : true;
},
setStage: function(stage, btn_flag = true) {
if (stage == 0) {
this.btn_action.textContent = _('Check for updates');
this.btn_action.classList.remove('hidden');
} else
if (stage == 1) {
this.btn_action.textContent = _('Update packages');
this.btn_action.classList.remove('hidden');
} else {
this.btn_action.classList.add('hidden');
}
if (stage > 1 && typeof(this.btn_action) == 'object') {
this.setBtnMode(1);
}
this.stage = stage;
},
checkUpdates: function() {
this.setStage(0);
this.setBtnMode(0);
this.pkg_url = null;
this.appendLog(_('Checking for updates...'));
let opt_list = [ '-c' ]; // check for updates
if (document.getElementById('cfg_exclude_prereleases').checked == false) {
opt_list.push('-p'); // include prereleases ZIP-files
}
let forced_reinstall = document.getElementById('cfg_forced_reinstall').checked;
let rpc_opt = { timeout: 20*1000 }
//rpc_opt.uid = 0; // run under root
let res = fs.exec(fn_update_pkg_sh, opt_list, null, rpc_opt).then(res => {
let log = res.stdout.trim();
this.appendLog(log);
let code = log.match(/^RESULT:\s*\(([^)]+)\)\s+.+$/m);
let pkg_url = log.match(/^ZAP_PKG_URL\s*=\s*(.+)$/m);
if (res.code == 0 && code && pkg_url) {
this.pkg_url = pkg_url[1];
code = code[1];
if (code == 'E' && !forced_reinstall) {
this.setStage(999);
return 0;
}
this.setStage(1);
this.setBtnMode(2); // enable all buttons
} else {
if (res.code != 0) {
this.appendLog('ERROR: Check for updates failed with error ' + res.code);
}
this.setStage(999);
}
return res.code;
}).catch(e => {
this.appendLog('ERROR: ' + _('Updates checking failed'));
this.appendLog('ERROR: ' + e);
this.setStage(999);
return 1;
}).finally(() => {
this.appendLog('=========================================================');
});
},
installUpdates: async function() {
this.setStage(1);
this.setBtnMode(0);
if (!this.pkg_url || this.pkg_url.length < 10) {
this.appendLog('ERROR: pkg_url = null');
this.setStage(999);
return 1;
}
this.appendLog(_('Install updates...'));
let opt_list = [ '-u', this.pkg_url ]; // update packages
if (document.getElementById('cfg_forced_reinstall').checked == true) {
opt_list.push('-f'); // forced reinstall if same version
}
let rpc_opt = { timeout: 5*1000 }
//rpc_opt.uid = 0; // run under root
const logFile = '/tmp/zapret2_pkg_install.log';
const rcFile = logFile + '.rc';
try {
await fs.exec('/bin/busybox', [ 'rm', '-f', logFile + '*' ], null, rpc_opt);
this.appendLog('Install log cleared.');
} catch (e) {
this.appendLog('ERROR: Failed to clear log file');
this.setStage(999);
return 1;
}
try {
let opt = [ logFile, fn_update_pkg_sh ];
//opt.push('-t'); opt.push('0'); // only for testing
opt.push(...opt_list);
let res = await fs.exec('/opt/zapret2/script-exec.sh', opt, null, rpc_opt);
if (res.code == 0) {
this.appendLog('Process started...');
} else {
this.appendLog('ERROR: cannot run ' + fn_update_pkg_sh + ' script! (error = ' + res.code + ')');
throw new Error('cannot run script');
}
} catch (e) {
this.appendLog('ERROR: Failed to start process: ' + e.message);
this.setStage(999);
return 1;
}
let lastLen = 0;
let retCode = -1;
let timerBusy = false;
let timer = setInterval(async () => {
if (timerBusy)
return; // skip iteration
timerBusy = true;
try {
let res = await fs.exec('/bin/cat', [ logFile ], null, rpc_opt);
if (res.stdout && res.stdout.length > lastLen) {
let log = res.stdout.slice(lastLen);
log = log.replace(/^ \* resolve_conffiles.*(?:\r?\n|$)/gm, '');
this.appendLog(log, '');
lastLen = res.stdout.length;
}
if (retCode < 0) {
let rc = await fs.exec('/bin/cat', [ rcFile ], null, rpc_opt);
if (rc.code != 0) {
throw new Error('cannot read file "' + rcFile + '"');
}
if (rc.stdout) {
retCode = parseInt(rc.stdout.trim(), 10);
}
}
if (retCode >= 0) {
clearInterval(timer);
this.appendLog('\n' + 'Process finished.');
if (res.stdout) {
let code = res.stdout.match(/^RESULT:\s*\(([^)]+)\)\s+.+$/m);
if (retCode == 0 && code && code[1] == '+') {
this.stage = 999;
this.btn_action.textContent = _('OK');
this.btn_action.disabled = false;
this.btn_cancel.disabled = true;
return 0;
}
}
this.appendLog('ERROR: Install updates failed with error ' + retCode);
this.setStage(999);
}
} catch (e) {
clearInterval(timer);
this.appendLog('ERROR: installUpdates: ' + e.message);
this.appendLog('ERROR: installUpdates: ' + e.stack?.trim().split('\n').pop());
this.setStage(999);
} finally {
timerBusy = false;
}
}, 500);
},
openUpdateDialog: function(pkg_arch) {
this.stage = 0;
this.pkg_arch = pkg_arch;
this.pkg_url = null;
let exclude_prereleases = E('label', [
E('input', { type: 'checkbox', id: 'cfg_exclude_prereleases', checked: true }),
' ', _('Exclude PreReleases')
]);
let forced_reinstall = E('label', [
E('input', { type: 'checkbox', id: 'cfg_forced_reinstall'}),
' ', _('Forced reinstall packages')
]);
this.logArea = E('textarea', {
'readonly': true,
'style': 'width:100%; height:400px; font-family: monospace;'
});
this.btn_cancel = E('button', {
'id': 'btn_cancel',
'name': 'btn_cancel',
'class': btn_style_warning,
}, _('Cancel'));
this.btn_cancel.onclick = ui.hideModal;
this.btn_action = E('button', {
'id': 'btn_action',
'name': 'btn_action',
'class': btn_style_action,
}, 'BUTTON_ACTION');
this.btn_action.onclick = ui.createHandlerFn(this, () => {
if (this.stage == 0) {
return this.checkUpdates();
}
if (this.stage == 1) {
return this.installUpdates();
}
return ui.hideModal();
});
this.setStage(0);
this.setBtnMode(2);
ui.showModal(_('Package update'), [
E('div', { 'class': 'cbi-section' }, [
exclude_prereleases,
E('br'), E('br'),
forced_reinstall,
E('br'), E('br'),
E('hr'),
this.logArea,
]),
E('div', { 'class': 'right' }, [
this.btn_cancel,
' ',
this.btn_action,
])
]);
}
});

View File

@@ -0,0 +1,41 @@
{
"admin/services/zapret2": {
"title": "Zapret2",
"order": 62,
"action": {
"type": "alias",
"path": "admin/services/zapret2/service"
},
"depends": {
"acl": [ "luci-app-zapret2" ],
"uci": { "zapret2": true }
}
},
"admin/services/zapret2/service": {
"title": "Service",
"order": 10,
"action": {
"type": "view",
"path": "zapret2/service"
}
},
"admin/services/zapret2/settings": {
"title": "Settings",
"order": 20,
"action": {
"type": "view",
"path": "zapret2/settings"
}
},
"admin/services/zapret2/dmnlog": {
"title": "Log Viewer",
"order": 30,
"action": {
"type": "view",
"path": "zapret2/dmnlog"
}
}
}

View File

@@ -0,0 +1,40 @@
{
"luci-app-zapret2": {
"description": "Grant access to zapret2 procedures",
"read": {
"cgi-io": [ "exec" ],
"file": {
"/opt/zapret2/config": [ "read" ],
"/opt/zapret2/ipset/*": [ "read" ],
"/opt/zapret2/init.d/openwrt/custom.d/*": [ "read" ],
"/etc/crontabs/root": [ "read" ],
"/tmp/zapret*": [ "read" ],
"/etc/init.d/zapret2*": [ "exec" ],
"/bin/ps*": [ "exec" ],
"/bin/cat*": [ "exec" ],
"/bin/busybox*": [ "exec" ],
"/bin/opkg*": [ "exec" ],
"/usr/bin/apk*": [ "exec" ],
"/usr/bin/find*": [ "exec" ],
"/opt/zapret2/restore-def-cfg.sh*": [ "exec" ],
"/opt/zapret2/script-exec.sh*": [ "exec" ],
"/opt/zapret2/update-pkg.sh*": [ "exec" ],
"/opt/zapret2/sync_config.sh*": [ "exec" ]
},
"uci": [ "zapret2", "network" ],
"ubus": {
"luci": [ "getInitList", "setInitAction" ],
"service": [ "list" ]
}
},
"write": {
"file": {
"/opt/zapret2/config": [ "write" ],
"/opt/zapret2/ipset/*": [ "write" ],
"/opt/zapret2/init.d/openwrt/custom.d/*": [ "write" ],
"/etc/crontabs/root": [ "write" ]
},
"uci": [ "zapret2" ]
}
}
}