mirror of
https://github.com/remittor/zapret-openwrt.git
synced 2025-12-06 03:26:49 +03:00
Add luci-app-zapret
This commit is contained in:
@@ -2,3 +2,15 @@
|
||||
# Copyright (с) 2024 remittor
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-zapret
|
||||
PKG_RELEASE:=20241008
|
||||
PKG_VERSION:=1.63-$(PKG_RELEASE)
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
LUCI_TITLE:=LuCI support for zapret
|
||||
LUCI_DEPENDS:=+zapret
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
'use strict';
|
||||
'require fs';
|
||||
'require poll';
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require view';
|
||||
'require view.zapret.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 important';
|
||||
|
||||
return view.extend({
|
||||
disableButtons: function(flag, btn, elems = [ ]) {
|
||||
let btn_start = elems[1] || document.getElementById("btn_start");
|
||||
let btn_destroy = elems[4] || document.getElementById("btn_destroy");
|
||||
let btn_enable = elems[2] || document.getElementById("btn_enable");
|
||||
let btn_update = elems[3] || document.getElementById("btn_update");
|
||||
|
||||
btn_start.disabled = flag;
|
||||
btn_update.disabled = true; // TODO
|
||||
btn_destroy.disabled = flag;
|
||||
if (btn === btn_update) {
|
||||
btn_enable.disabled = false;
|
||||
} else {
|
||||
btn_enable.disabled = flag;
|
||||
}
|
||||
},
|
||||
|
||||
getAppStatus: function() {
|
||||
return Promise.all([
|
||||
{ code: -1 }, //fs.exec(tools.execPath, [ 'raw-status' ]),
|
||||
{ code: -1 }, //fs.exec(tools.execPath, [ 'vpn-route-status' ]),
|
||||
tools.getInitStatus(tools.appName),
|
||||
uci.load(tools.appName),
|
||||
]).catch(e => {
|
||||
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||
+ ': %s [ %s | %s | %s ]'.format(
|
||||
e.message, tools.execPath, 'tools.getInitStatus', 'uci.zapret'
|
||||
)));
|
||||
});
|
||||
},
|
||||
|
||||
setAppStatus: function(status_array, elems = [ ], force_app_code = 0) {
|
||||
let section = uci.get(tools.appName, 'config');
|
||||
if (!status_array || section == null || typeof(section) !== 'object') {
|
||||
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(1);
|
||||
ui.addNotification(null, E('p', _('Unable to read the contents') + ': setAppStatus()'));
|
||||
this.disableButtons(true, null, elems);
|
||||
return;
|
||||
}
|
||||
|
||||
let app_status_code = (force_app_code) ? force_app_code : status_array[0].code;
|
||||
let vpn_route_status_code = status_array[1].code;
|
||||
let enabled_flag = status_array[2];
|
||||
let z_fwtype = section.FWTYPE;
|
||||
let z_mode = section.MODE;
|
||||
let bllist_preset = 'user_only';
|
||||
|
||||
let btn_enable = elems[2] || document.getElementById('btn_enable');
|
||||
if (enabled_flag == true) {
|
||||
btn_enable.onclick = ui.createHandlerFn(this, this.serviceAction, 'disable', 'btn_enable');
|
||||
btn_enable.textContent = _('Enabled');
|
||||
btn_enable.className = btn_style_positive;
|
||||
} else {
|
||||
btn_enable.onclick = ui.createHandlerFn(this, this.serviceAction, 'enable', 'btn_enable');
|
||||
btn_enable.textContent = _('Disabled');
|
||||
btn_enable.className = btn_style_negative;
|
||||
}
|
||||
|
||||
let btn_start = elems[1] || document.getElementById('btn_start');
|
||||
let btn_update = elems[3] || document.getElementById('btn_update');
|
||||
let btn_destroy = elems[4] || document.getElementById('btn_destroy');
|
||||
|
||||
let btnStartStateOn = () => {
|
||||
btn_start.onclick = ui.createHandlerFn(this, this.appAction, 'stop', 'btn_start');
|
||||
btn_start.textContent = _('Enabled');
|
||||
btn_start.className = btn_style_positive;
|
||||
};
|
||||
|
||||
let btnStartStateOff = () => {
|
||||
btn_start.onclick = ui.createHandlerFn(this, this.appAction, 'start', 'btn_start');
|
||||
btn_start.textContent = _('Disabled');
|
||||
btn_start.className = btn_style_negative;
|
||||
};
|
||||
|
||||
if (app_status_code == -1) {
|
||||
this.disableButtons(false, null, elems);
|
||||
btnStartStateOn();
|
||||
}
|
||||
else if (app_status_code == 0) {
|
||||
this.disableButtons(false, null, elems);
|
||||
btnStartStateOn();
|
||||
btn_destroy.disabled = false;
|
||||
btn_update.disabled = false;
|
||||
}
|
||||
else if (app_status_code == 2) {
|
||||
this.disableButtons(false, null, elems);
|
||||
btnStartStateOff();
|
||||
btn_update.disabled = true;
|
||||
}
|
||||
else if (app_status_code == 3) {
|
||||
btnStartStateOff();
|
||||
this.disableButtons(true, btn_start, elems);
|
||||
}
|
||||
else if (app_status_code == 4) {
|
||||
btnStartStateOn();
|
||||
this.disableButtons(true, btn_update, elems);
|
||||
}
|
||||
else {
|
||||
ui.addNotification(null, E('p', _('Error')
|
||||
+ ' %s: return code = %s'.format(tools.execPath, app_status_code)));
|
||||
this.disableButtons(true, null, elems);
|
||||
}
|
||||
|
||||
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(app_status_code, z_fwtype, bllist_preset);
|
||||
|
||||
if (!poll.active()) {
|
||||
poll.start();
|
||||
}
|
||||
},
|
||||
|
||||
serviceAction: function(action, button) {
|
||||
if (button) {
|
||||
let elem = document.getElementById(button);
|
||||
this.disableButtons(true, elem);
|
||||
}
|
||||
|
||||
poll.stop();
|
||||
|
||||
return tools.handleServiceAction(tools.appName, action).then(() => {
|
||||
return this.getAppStatus().then(
|
||||
(status_array) => {
|
||||
this.setAppStatus(status_array);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
appAction: function(action, button) {
|
||||
if (button) {
|
||||
let elem = document.getElementById(button);
|
||||
this.disableButtons(true, elem);
|
||||
}
|
||||
|
||||
poll.stop();
|
||||
|
||||
if (action === 'update') {
|
||||
this.getAppStatus().then(
|
||||
(status_array) => {
|
||||
this.setAppStatus(status_array, [], 4);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return fs.exec_direct(tools.execPath, [ action ]).then(res => {
|
||||
return this.getAppStatus().then(
|
||||
(status_array) => {
|
||||
this.setAppStatus(status_array);
|
||||
ui.hideModal();
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
statusPoll: function() {
|
||||
this.getAppStatus().then(
|
||||
L.bind(this.setAppStatus, this)
|
||||
);
|
||||
},
|
||||
|
||||
dialogDestroy: function(ev) {
|
||||
ev.target.blur();
|
||||
let cancel_button = E('button', {
|
||||
'class': btn_style_neutral,
|
||||
'click': ui.hideModal,
|
||||
}, _('Cancel'));
|
||||
|
||||
let shutdown_btn = E('button', {
|
||||
'class': btn_style_warning,
|
||||
}, _('Shutdown'));
|
||||
shutdown_btn.onclick = ui.createHandlerFn(this, () => {
|
||||
cancel_button.disabled = true;
|
||||
return this.appAction('destroy');
|
||||
});
|
||||
|
||||
ui.showModal(_('Shutdown'), [
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('p', _('The service will be disabled. Continue?')),
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
shutdown_btn,
|
||||
' ',
|
||||
cancel_button,
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return this.getAppStatus();
|
||||
},
|
||||
|
||||
render: function(status_array) {
|
||||
if (!status_array) {
|
||||
return;
|
||||
}
|
||||
|
||||
let section = uci.get(tools.appName, 'config');
|
||||
|
||||
let status_string = E('div', {
|
||||
'id' : 'status',
|
||||
'name' : 'status',
|
||||
'class': 'cbi-section-node',
|
||||
});
|
||||
|
||||
let layout = E('div', { 'class': 'cbi-section-node' });
|
||||
|
||||
function layout_append(elem, title, descr) {
|
||||
descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : '';
|
||||
layout.append(
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title', 'for': elem.id + '_hidden' || null }, title),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('div', {}, elem),
|
||||
E('input', {
|
||||
'id' : elem.id + '_hidden',
|
||||
'type': 'hidden',
|
||||
}),
|
||||
descr,
|
||||
]),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
let btn_start = E('button', {
|
||||
'id' : 'btn_start',
|
||||
'name' : 'btn_start',
|
||||
'class': btn_style_action,
|
||||
}, _('Enable'));
|
||||
layout_append(btn_start, _('Service'));
|
||||
|
||||
let btn_enable = E('button', {
|
||||
'id' : 'btn_enable',
|
||||
'name' : 'btn_enable',
|
||||
'class': btn_style_positive,
|
||||
}, _('Enable'));
|
||||
layout_append(btn_enable, _('Run at startup'));
|
||||
|
||||
let btn_update = E('button', {
|
||||
'id' : 'btn_update',
|
||||
'name' : 'btn_update',
|
||||
'class': btn_style_action,
|
||||
}, _('Update'));
|
||||
btn_update.onclick = ui.createHandlerFn(this, () => { this.appAction('update', 'btn_update') });
|
||||
layout_append(btn_update, _('Update blacklist'));
|
||||
|
||||
let btn_destroy = E('button', {
|
||||
'id' : 'btn_destroy',
|
||||
'name' : 'btn_destroy',
|
||||
'class': btn_style_negative,
|
||||
}, _('Shutdown'));
|
||||
btn_destroy.onclick = L.bind(this.dialogDestroy, this);
|
||||
|
||||
layout_append(btn_destroy, _('Shutdown'), _('Complete service shutdown'));
|
||||
|
||||
let elems = [ status_string, btn_start, btn_enable, btn_update, btn_destroy ];
|
||||
this.setAppStatus(status_array, elems);
|
||||
|
||||
poll.add(L.bind(this.statusPoll, this));
|
||||
|
||||
let url1 = 'https://github.com/bol-van/zapret';
|
||||
let url2 = 'https://github.com/remittor/zapret-openwrt';
|
||||
|
||||
return E([
|
||||
E('h2', { 'class': 'fade-in' }, _('Zapret')),
|
||||
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('hr'),
|
||||
]),
|
||||
E('div', { 'class': 'cbi-section fade-in' },
|
||||
layout
|
||||
),
|
||||
]);
|
||||
},
|
||||
|
||||
handleSave : null,
|
||||
handleSaveApply: null,
|
||||
handleReset : null,
|
||||
});
|
||||
@@ -0,0 +1,210 @@
|
||||
'use strict';
|
||||
'require fs';
|
||||
'require form';
|
||||
'require tools.widgets as widgets';
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require view';
|
||||
'require view.zapret.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, _('Zapret') + ' - ' + _('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.ListValue, 'MODE', _('MODE'));
|
||||
o.value('nfqws', 'nfqws');
|
||||
//o.value('tpws', 'tpws');
|
||||
|
||||
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.ListValue, 'INIT_APPLY_FW', _('INIT_APPLY_FW'));
|
||||
o.value('0', 'False');
|
||||
o.value('1', 'True');
|
||||
|
||||
o = s.taboption(tabname, form.ListValue, 'DISABLE_IPV4', _('DISABLE_IPV4'));
|
||||
o.value('0', 'False');
|
||||
o.value('1', 'True');
|
||||
|
||||
o = s.taboption(tabname, form.ListValue, 'DISABLE_IPV6', _('DISABLE_IPV6'));
|
||||
o.value('0', 'False');
|
||||
o.value('1', 'True');
|
||||
|
||||
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.ListValue, 'MODE_HTTP', _('MODE_HTTP'));
|
||||
o.value('0', 'False');
|
||||
o.value('1', 'True');
|
||||
|
||||
o = s.taboption(tabname, form.ListValue, 'MODE_HTTP_KEEPALIVE', _('MODE_HTTP_KEEPALIVE'));
|
||||
o.value('0', 'False');
|
||||
o.value('1', 'True');
|
||||
|
||||
o = s.taboption(tabname, form.ListValue, 'MODE_HTTPS', _('MODE_HTTPS'));
|
||||
o.value('0', 'False');
|
||||
o.value('1', 'True');
|
||||
|
||||
o = s.taboption(tabname, form.ListValue, 'MODE_QUIC', _('MODE_QUIC'));
|
||||
o.value('0', 'False');
|
||||
o.value('1', 'True');
|
||||
|
||||
/* NFQWS_OPT_DESYNC tab */
|
||||
|
||||
tabname = 'nfqws_params';
|
||||
s.tab(tabname, _('NFQWS options'));
|
||||
|
||||
let add_delim = function() {
|
||||
o = s.taboption(tabname, form.DummyValue, '_hr');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 620px; height: 1px; margin: 1px 0 1px; border-top: 1px solid;">';
|
||||
};
|
||||
|
||||
let add_param = function(param, locname = null, rows = 10) {
|
||||
if (!locname)
|
||||
locname = param;
|
||||
o = s.taboption(tabname, form.Button, '_' + param + '_btn', locname);
|
||||
o.onclick = () => new tools.longstrEditDialog('config', param, param, locname, rows).show();
|
||||
o.inputtitle = _('Edit');
|
||||
o.inputstyle = 'edit btn';
|
||||
o = s.taboption(tabname, form.DummyValue, '_' + param);
|
||||
o.rawhtml = false;
|
||||
o.cfgvalue = function(section_id) {
|
||||
var name = uci.get(tools.appName, section_id, param);
|
||||
if (name == null || name == "")
|
||||
name = "<EMPTY>";
|
||||
return name;
|
||||
};
|
||||
};
|
||||
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_SUFFIX');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTP');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTP_SUFFIX');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTPS');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTPS_SUFFIX');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTP6');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTP6_SUFFIX');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTPS6');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_HTTPS6_SUFFIX');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_QUIC');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_QUIC_SUFFIX');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_QUIC6');
|
||||
add_delim();
|
||||
add_param('NFQWS_OPT_DESYNC_QUIC6_SUFFIX');
|
||||
|
||||
/* Blacklist settings */
|
||||
|
||||
tabname = 'blacklist_tab';
|
||||
s.tab(tabname, _('Blacklist settings'));
|
||||
|
||||
let user_entries_edit = new tools.fileEditDialog(
|
||||
tools.userEntriesFile,
|
||||
_('User entries'),
|
||||
_('One hostname per line.<br />Examples:'),
|
||||
'<code>domain.net<br />sub.domain.com<br />googlevideo.com</code>',
|
||||
15
|
||||
);
|
||||
o = s.taboption(tabname, form.Button, '_user_entries_btn', _('User hostname entries'));
|
||||
o.onclick = () => user_entries_edit.show();
|
||||
o.inputtitle = _('Edit');
|
||||
o.inputstyle = 'edit btn';
|
||||
|
||||
let ip_filter_edit = new tools.fileEditDialog(
|
||||
tools.ipFilterFile,
|
||||
_('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
|
||||
);
|
||||
o = s.taboption(tabname, form.Button, '_ip_filter_btn', _('User IP entries'));
|
||||
o.onclick = () => ip_filter_edit.show();
|
||||
o.inputtitle = _('Edit');
|
||||
o.inputstyle = 'edit btn';
|
||||
|
||||
let map_promise = m.render();
|
||||
map_promise.then(node => node.classList.add('fade-in'));
|
||||
return map_promise;
|
||||
},
|
||||
|
||||
handleSaveApply: function(ev, mode) {
|
||||
return this.handleSave(ev).then(() => {
|
||||
ui.changes.apply(mode == '0');
|
||||
if (this.appStatusCode != 1 && this.appStatusCode != 2) {
|
||||
window.setTimeout(() => fs.exec(tools.execPath, [ 'restart' ]), 3000);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,348 @@
|
||||
'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({
|
||||
appName : 'zapret',
|
||||
execPath : '/opt/zapret/init.d/openwrt/zapret',
|
||||
parsersDir : '/usr/libexec/ruantiblock',
|
||||
userEntriesFile : '/opt/zapret/ipset/zapret-hosts-user.txt',
|
||||
ipFilterFile : '/opt/zapret/ipset/zapret-ip-user.txt',
|
||||
|
||||
infoLabelStarting : '<span class="label-status starting">' + _('Starting') + '</span>',
|
||||
infoLabelRunning : '<span class="label-status running">' + _('Enabled') + '</span>',
|
||||
infoLabelUpdating : '<span class="label-status updating">' + _('Updating') + '</span>',
|
||||
infoLabelStopped : '<span class="label-status stopped">' + _('Disabled') + '</span>',
|
||||
infoLabelError : '<span class="label-status error">' + _('Error') + '</span>',
|
||||
|
||||
callInitStatus: rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'getInitList',
|
||||
params: [ 'name' ],
|
||||
expect: { '': {} }
|
||||
}),
|
||||
|
||||
callInitAction: rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'setInitAction',
|
||||
params: [ 'name', 'action' ],
|
||||
expect: { result: false }
|
||||
}),
|
||||
|
||||
getInitStatus: function(name) {
|
||||
return this.callInitStatus(name).then(res => {
|
||||
if (res) {
|
||||
return res[name].enabled;
|
||||
} else {
|
||||
throw _('Command failed');
|
||||
}
|
||||
}).catch(e => {
|
||||
ui.addNotification(null, E('p', _('Failed to get %s init status: %s').format(name, e)));
|
||||
});
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
makeStatusString: function(app_status_code, fwtype, bllist_preset) {
|
||||
let app_status_label;
|
||||
let spinning = '';
|
||||
|
||||
switch(app_status_code) {
|
||||
case 0:
|
||||
app_status_label = this.infoLabelRunning;
|
||||
break;
|
||||
case 2:
|
||||
app_status_label = this.infoLabelStopped;
|
||||
break;
|
||||
case 3:
|
||||
app_status_label = this.infoLabelStarting;
|
||||
spinning = ' spinning';
|
||||
break;
|
||||
case 4:
|
||||
app_status_label = this.infoLabelUpdating;
|
||||
spinning = ' spinning';
|
||||
break;
|
||||
default:
|
||||
app_status_label = this.infoLabelError;
|
||||
return `<table class="table">
|
||||
<tr class="tr">
|
||||
<td class="td left" style="min-width:33%%">
|
||||
${_('Status')}:
|
||||
</td>
|
||||
<td class="td left">
|
||||
${app_status_label}
|
||||
</td>
|
||||
</tr>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
return `<table class="table">
|
||||
<tr class="tr">
|
||||
<td class="td left" style="min-width:33%%">
|
||||
${_('Status')}:
|
||||
</td>
|
||||
<td class="td left%s">
|
||||
%s %s
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="tr">
|
||||
<td class="td left">
|
||||
${_('FW type')}:
|
||||
</td>
|
||||
<td class="td left">
|
||||
%s
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="tr">
|
||||
<td class="td left">
|
||||
${_('Blacklist update mode')}:
|
||||
</td>
|
||||
<td class="td left">
|
||||
%s
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`.format(
|
||||
spinning,
|
||||
app_status_label,
|
||||
'',
|
||||
fwtype,
|
||||
_('user entries only')
|
||||
);
|
||||
},
|
||||
|
||||
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.handleSave),
|
||||
}, _('Save')),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
|
||||
handleSave: 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) {
|
||||
this.cfgsec = cfgsec;
|
||||
this.cfgparam = cfgparam;
|
||||
this.title = title;
|
||||
this.desc = desc;
|
||||
this.rows = rows;
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return uci.get('zapret', this.cfgsec, this.cfgparam);
|
||||
},
|
||||
|
||||
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.handleSave),
|
||||
}, _('Save')),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
|
||||
handleSave: function(ev) {
|
||||
let txt = document.getElementById('widget.modal_content');
|
||||
let value = txt.value.trim().replace(/\r\n/g, ' ').replace(/\r/g, ' ').replace(/\n/g, ' ').trim();
|
||||
|
||||
uci.set('zapret', this.cfgsec, this.cfgparam, value);
|
||||
|
||||
return uci.save()
|
||||
.then(L.bind(ui.changes.init, ui.changes))
|
||||
//.then(L.bind(ui.changes.apply, ui.changes))
|
||||
.then(ui.addNotification(null, E('p', _('Contents have been saved.')), 'info'))
|
||||
.catch(e => {
|
||||
ui.addNotification(null, E('p', _('Unable to save the contents') + ': %s'.format(e.message)));
|
||||
}).finally(() => {
|
||||
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')) );
|
||||
let content = this.load();
|
||||
ui.hideModal();
|
||||
if (content == null) {
|
||||
return this.error('Cannot load parameter');
|
||||
}
|
||||
return this.render(content);
|
||||
},
|
||||
}),
|
||||
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"admin/services/zapret": {
|
||||
"title": "Zapret",
|
||||
"order": 61,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
"path": "admin/services/zapret/service"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-zapret" ],
|
||||
"fs": {
|
||||
"/opt/zapret/sync_config.sh": "executable",
|
||||
"/etc/init.d/zapret": "executable"
|
||||
},
|
||||
"uci": { "zapret": true }
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/zapret/service": {
|
||||
"title": "Service",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "zapret/service"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/zapret/settings": {
|
||||
"title": "Settings",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "zapret/settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"luci-app-zapret": {
|
||||
"description": "Grant access to zapret procedures",
|
||||
"read": {
|
||||
"cgi-io": [ "exec" ],
|
||||
"file": {
|
||||
"/opt/zapret/config": [ "read" ],
|
||||
"/opt/zapret/ipset/*": [ "read" ],
|
||||
"/etc/crontabs/root": [ "read" ],
|
||||
"/etc/init.d/zapret*": [ "exec" ],
|
||||
"/opt/zapret/sync_config.sh": [ "exec" ]
|
||||
},
|
||||
"uci": [ "zapret", "network", "firewall" ],
|
||||
"ubus": {
|
||||
"luci": [ "getInitList", "setInitAction" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/opt/zapret/config": [ "write" ],
|
||||
"/opt/zapret/ipset/*.txt": [ "write" ],
|
||||
"/etc/crontabs/root": [ "write" ]
|
||||
},
|
||||
"uci": [ "zapret" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (<EFBFBD>) 2024 remittor
|
||||
# Copyright (c) 2024 remittor
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
@@ -99,6 +99,7 @@ define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_CONF) ./ipset/zapret-ip-user.txt $(1)/opt/zapret/ipset/zapret-ip-user.txt
|
||||
$(INSTALL_CONF) ./ipset/zapret-ip-user-exclude.txt $(1)/opt/zapret/ipset/zapret-ip-user-exclude.txt
|
||||
$(INSTALL_CONF) ./ipset/zapret-ip-user-ipban.txt $(1)/opt/zapret/ipset/zapret-ip-user-ipban.txt
|
||||
$(INSTALL_BIN) ./sync_config.sh $(1)/opt/zapret/sync_config.sh
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/postinst
|
||||
|
||||
67
zapret/sync_config.sh
Executable file
67
zapret/sync_config.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) 2024 remittor
|
||||
|
||||
EXEDIR=/opt/zapret
|
||||
ZAPRET_BASE=/opt/zapret
|
||||
ZAPRET_CONFIG="$ZAPRET_BASE/config"
|
||||
ZAPRET_CFG=/etc/config/zapret
|
||||
|
||||
function get_sed_compat
|
||||
{
|
||||
local str=$( ( echo $1|sed -r 's/([\$\.\*\/\[\\^])/\\\1/g'|sed 's/[]]/\\]/g' )>&1 )
|
||||
echo "$str"
|
||||
}
|
||||
|
||||
function set_param_value
|
||||
{
|
||||
local param=$1
|
||||
local value=$( get_sed_compat "$2" )
|
||||
local fname=${3:-$ZAPRET_CONFIG}
|
||||
sed -i "s/^$param=.*/$param=$value/g" $fname
|
||||
}
|
||||
|
||||
function set_param_value_str
|
||||
{
|
||||
local param=$1
|
||||
local value=$( get_sed_compat "$2" )
|
||||
local fname=${3:-$ZAPRET_CONFIG}
|
||||
sed -i "s/^$param=.*/$param=\"$value\"/g" $fname
|
||||
}
|
||||
|
||||
function sync_param
|
||||
{
|
||||
local param=$1
|
||||
local vtype=$2
|
||||
local value=$( uci -q get zapret.@main[0].$param )
|
||||
if [ "$vtype" = "str" ]; then
|
||||
set_param_value_str $param "$value"
|
||||
else
|
||||
set_param_value $param $value
|
||||
fi
|
||||
}
|
||||
|
||||
sync_param MODE
|
||||
sync_param FLOWOFFLOAD
|
||||
sync_param INIT_APPLY_FW
|
||||
sync_param DISABLE_IPV4
|
||||
sync_param DISABLE_IPV6
|
||||
sync_param MODE_FILTER
|
||||
sync_param NFQWS_OPT_DESYNC str
|
||||
sync_param NFQWS_OPT_DESYNC_SUFFIX str
|
||||
sync_param MODE_HTTP
|
||||
sync_param MODE_HTTP_KEEPALIVE
|
||||
sync_param NFQWS_OPT_DESYNC_HTTP str
|
||||
sync_param NFQWS_OPT_DESYNC_HTTP_SUFFIX str
|
||||
sync_param NFQWS_OPT_DESYNC_HTTP6 str
|
||||
sync_param NFQWS_OPT_DESYNC_HTTP6_SUFFIX str
|
||||
sync_param MODE_HTTPS
|
||||
sync_param NFQWS_OPT_DESYNC_HTTPS str
|
||||
sync_param NFQWS_OPT_DESYNC_HTTPS_SUFFIX str
|
||||
sync_param NFQWS_OPT_DESYNC_HTTPS6 str
|
||||
sync_param NFQWS_OPT_DESYNC_HTTPS6_SUFFIX str
|
||||
sync_param MODE_QUIC
|
||||
sync_param NFQWS_OPT_DESYNC_QUIC str
|
||||
sync_param NFQWS_OPT_DESYNC_QUIC_SUFFIX str
|
||||
sync_param NFQWS_OPT_DESYNC_QUIC6 str
|
||||
sync_param NFQWS_OPT_DESYNC_QUIC6_SUFFIX str
|
||||
|
||||
Reference in New Issue
Block a user