Add luci-app-zapret

This commit is contained in:
remittor
2024-10-11 14:49:19 +03:00
parent a7289f779a
commit 5fc039e165
8 changed files with 999 additions and 1 deletions

View File

@@ -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

View File

@@ -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,
});

View File

@@ -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);
}
});
},
});

View File

@@ -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);
},
}),
});

View File

@@ -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"
}
}
}

View File

@@ -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" ]
}
}
}

View File

@@ -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
View 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