mirror of
https://github.com/remittor/zapret-openwrt.git
synced 2026-01-27 04:40:34 +03:00
Rename all directories
This commit is contained in:
32
luci-app-zapret2/Makefile
Normal file
32
luci-app-zapret2/Makefile
Normal 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
|
||||
@@ -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
|
||||
});
|
||||
@@ -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 += '   ';
|
||||
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', { }, ' ');
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
@@ -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);
|
||||
//}
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
})
|
||||
},
|
||||
}),
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user