mirror of
https://github.com/remittor/zapret-openwrt.git
synced 2025-12-09 13:06:50 +03:00
456 lines
16 KiB
JavaScript
456 lines
16 KiB
JavaScript
'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 : '/etc/init.d/zapret',
|
|
syncCfgPath : '/opt/zapret/sync_config.sh',
|
|
|
|
hostsUserFN : '/opt/zapret/ipset/zapret-hosts-user.txt',
|
|
hostsUserExcludeFN: '/opt/zapret/ipset/zapret-hosts-user-exclude.txt',
|
|
iplstExcludeFN : '/opt/zapret/ipset/zapret-ip-exclude.txt',
|
|
iplstUserFN : '/opt/zapret/ipset/zapret-ip-user.txt',
|
|
iplstUserExcludeFN: '/opt/zapret/ipset/zapret-ip-user-exclude.txt',
|
|
custFileMax : 4,
|
|
custFileTemplate : '/opt/zapret/ipset/cust%s.txt',
|
|
|
|
autoHostListFN : '/opt/zapret/ipset/zapret-hosts-auto.txt',
|
|
autoHostListDbgFN : '/opt/zapret/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 },
|
|
},
|
|
|
|
callInitState: rpc.declare({
|
|
object: 'luci',
|
|
method: 'getInitList',
|
|
params: [ 'name' ],
|
|
expect: { '': {} }
|
|
}),
|
|
|
|
callInitAction: rpc.declare({
|
|
object: 'luci',
|
|
method: 'setInitAction',
|
|
params: [ 'name', 'action' ],
|
|
expect: { result: false }
|
|
}),
|
|
|
|
getInitState: function(name) {
|
|
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)));
|
|
});
|
|
},
|
|
|
|
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;
|
|
},
|
|
|
|
decode_pkg_list: function(pkg_list) {
|
|
let pkg_dict = { };
|
|
let lines = pkg_list.trim().split('\n');
|
|
for (let i = 0; i < lines.length; i++) {
|
|
let line = lines[i].trim();
|
|
if (line.length >= 4) {
|
|
let word_list = line.split(' - ');
|
|
let name = word_list[0].trim();
|
|
let ver = word_list[1].trim();
|
|
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 (svc_info.code != 0) {
|
|
return -1;
|
|
}
|
|
if (proc_list.code != 0) {
|
|
return -2;
|
|
}
|
|
let plist = this.get_pid_list(proc_list.stdout);
|
|
|
|
let jdata = JSON.parse(svc_info.stdout);
|
|
if (typeof(jdata) !== 'object') {
|
|
return -3;
|
|
}
|
|
if (typeof(jdata.zapret) == 'object') {
|
|
result.dmn.inited = true;
|
|
let dmn_list = jdata.zapret.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, fwtype, 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 update_mode = _('user entries only');
|
|
|
|
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}>
|
|
${_('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}>
|
|
${_('FW type')}:
|
|
</td>
|
|
<td class="td left">
|
|
${fwtype}
|
|
</td>
|
|
</tr>
|
|
<tr class="tr">
|
|
<td class="td left" ${td_name_style}>
|
|
${_('Blacklist update mode')}:
|
|
</td>
|
|
<td class="td left">
|
|
${update_mode}
|
|
</td>
|
|
</tr>
|
|
<tr class="tr">
|
|
<td class="td left" ${td_name_style}>
|
|
</td>
|
|
<td class="td left">
|
|
</td>
|
|
</tr>
|
|
</table>`;
|
|
return out;
|
|
},
|
|
|
|
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() {
|
|
let value = uci.get('zapret', this.cfgsec, this.cfgparam);
|
|
if (typeof(value) === 'string') {
|
|
return value.trim();
|
|
}
|
|
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.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();
|
|
if (value == "") {
|
|
value = "\t";
|
|
}
|
|
uci.set('zapret', this.cfgsec, this.cfgparam, value);
|
|
uci.save();
|
|
let elem = document.getElementById("cbi-zapret-" + this.cfgsec + "-_" + this.cfgparam);
|
|
if (elem) {
|
|
elem.querySelector('div').textContent = value;
|
|
}
|
|
ui.hideModal();
|
|
/*
|
|
return uci.save()
|
|
.then(L.bind(ui.changes.init, ui.changes))
|
|
.then(L.bind(ui.changes.displayChanges, 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);
|
|
},
|
|
}),
|
|
|
|
});
|