luci: Fix show actual service status

This commit is contained in:
remittor
2024-10-23 13:26:28 +03:00
parent 144b9369b9
commit ed09612065
3 changed files with 242 additions and 170 deletions

View File

@@ -14,111 +14,92 @@ const btn_style_warning = 'btn cbi-button-negative';
const btn_style_success = 'btn cbi-button-success important'; const btn_style_success = 'btn cbi-button-success important';
return view.extend({ return view.extend({
disableButtons: function(flag, btn, elems = [ ]) { disableButtons: function(flag, btn, elems = { }) {
let btn_start = elems[1] || document.getElementById("btn_start"); let btn_enable = elems.btn_enable || document.getElementById('btn_enable');
//let btn_destroy = elems[4] || document.getElementById("btn_destroy"); let btn_disable = elems.btn_disable || document.getElementById('btn_disable');
let btn_enable = elems[2] || document.getElementById("btn_enable"); let btn_start = elems.btn_start || document.getElementById('btn_start');
let btn_update = elems[3] || document.getElementById("btn_update"); let btn_restart = elems.btn_restart || document.getElementById('btn_restart');
let btn_stop = elems.btn_stop || document.getElementById('btn_stop');
let btn_update = elems.btn_update || document.getElementById('btn_update');
btn_enable.disabled = flag;
btn_disable.disabled = flag;
btn_start.disabled = flag; btn_start.disabled = flag;
btn_restart.disabled = flag;
btn_stop.disabled = flag;
btn_update.disabled = true; // TODO btn_update.disabled = true; // TODO
//btn_destroy.disabled = flag;
if (btn === btn_update) {
btn_enable.disabled = false;
} else {
btn_enable.disabled = flag;
}
}, },
getAppStatus: function() { getAppStatus: function() {
return Promise.all([ return Promise.all([
{ code: -1 }, //fs.exec(tools.execPath, [ 'raw-status' ]), tools.getInitState(tools.appName), // svc_state
{ code: -1 }, //fs.exec(tools.execPath, [ 'vpn-route-status' ]), fs.exec(tools.execPath, [ 'info' ]), // svc_info
tools.getInitStatus(tools.appName), fs.exec('/bin/ps'), // process list
//L.resolveDefault(fs.read(tools.tokenFile), 0), uci.load(tools.appName), // config
uci.load(tools.appName),
]).catch(e => { ]).catch(e => {
ui.addNotification(null, E('p', _('Unable to execute or read contents') ui.addNotification(null, E('p', _('Unable to execute or read contents')
+ ': %s [ %s | %s | %s ]'.format( + ': %s [ %s | %s | %s ]'.format(
e.message, tools.execPath, 'tools.getInitStatus', 'uci.zapret' e.message, tools.execPath, 'tools.getInitState', 'uci.zapret'
))); )));
}); });
}, },
setAppStatus: function(status_array, elems = [ ], force_app_code = 0) { setAppStatus: function(status_array, elems = { }, force_app_status = 0) {
let section = uci.get(tools.appName, 'config'); let cfg = uci.get(tools.appName, 'config');
if (!status_array || section == null || typeof(section) !== 'object') { if (!status_array || cfg == null || typeof(cfg) !== 'object') {
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(1); 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()')); ui.addNotification(null, E('p', _('Unable to read the contents') + ': setAppStatus()'));
this.disableButtons(true, null, elems); this.disableButtons(true, null, elems);
return; return;
} }
let svc_autorun = status_array[0] ? true : false;
let app_status_code = (force_app_code) ? force_app_code : status_array[0].code; let svc_info = status_array[1]; // stdout: JSON as text
let vpn_route_status_code = status_array[1].code; let proc_list = status_array[2]; // stdout: multiline text
let enabled_flag = status_array[2]; if (svc_info.code != 0) {
let z_fwtype = section.FWTYPE; ui.addNotification(null, E('p', _('Unable to read the service info') + ': setAppStatus()'));
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); this.disableButtons(true, null, elems);
return;
} }
if (proc_list.code != 0) {
ui.addNotification(null, E('p', _('Unable to read process list') + ': setAppStatus()'));
this.disableButtons(true, null, elems);
return;
}
let svcinfo;
if (force_app_status) {
svcinfo = force_app_status;
} else {
svcinfo = tools.decode_svc_info(svc_autorun, svc_info, proc_list, cfg);
}
let btn_enable = elems.btn_enable || document.getElementById('btn_enable');
let btn_disable = elems.btn_disable || document.getElementById('btn_disable');
(elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(app_status_code, z_fwtype, bllist_preset); let btn_start = elems.btn_start || document.getElementById('btn_start');
let btn_restart = elems.btn_restart || document.getElementById('btn_restart');
let btn_stop = elems.btn_stop || document.getElementById('btn_stop');
let btn_update = elems.btn_update || document.getElementById('btn_update');
btn_update.disabled = true; // TODO
if (Number.isInteger(svcinfo)) {
ui.addNotification(null, E('p', _('Error')
+ ' %s: return code = %s'.format('decode_svc_info', svcinfo + ' ')));
this.disableButtons(true, null, elems);
} else {
btn_enable.disabled = (svc_autorun) ? true : false;
btn_disable.disabled = (svc_autorun) ? false : true;
if (svcinfo.dmn.total == 0) {
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, cfg.FWTYPE, 'user_only');
if (!poll.active()) { if (!poll.active()) {
poll.start(); poll.start();
@@ -130,12 +111,33 @@ return view.extend({
let elem = document.getElementById(button); let elem = document.getElementById(button);
this.disableButtons(true, elem); this.disableButtons(true, elem);
} }
poll.stop(); poll.stop();
let _this = this; let _this = this;
return fs.exec('/opt/zapret/sync_config.sh') 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) {
if (button) {
let elem = document.getElementById(button);
this.disableButtons(true, elem);
}
poll.stop();
let _this = this;
return fs.exec(tools.syncCfgPath)
.then(function(res) { .then(function(res) {
if (res.code != 0) { if (res.code != 0) {
ui.addNotification(null, E('p', _('Unable to run sync_config.sh script.') + ' res.code = ' + res.code)); ui.addNotification(null, E('p', _('Unable to run sync_config.sh script.') + ' res.code = ' + res.code));
@@ -145,13 +147,7 @@ return view.extend({
} }
); );
} }
return tools.handleServiceAction(tools.appName, action).then(() => { return _this.serviceAction(action, null);
return _this.getAppStatus().then(
(status_array) => {
_this.setAppStatus(status_array);
}
);
});
}) })
.catch(e => { .catch(e => {
ui.addNotification(null, E('p', _('Unable to run sync_config.sh script.') + ' Error: ' + e.message)); ui.addNotification(null, E('p', _('Unable to run sync_config.sh script.') + ' Error: ' + e.message));
@@ -225,8 +221,7 @@ return view.extend({
if (!status_array) { if (!status_array) {
return; return;
} }
let cfg = uci.get(tools.appName, 'config');
let section = uci.get(tools.appName, 'config');
let status_string = E('div', { let status_string = E('div', {
'id' : 'status', 'id' : 'status',
@@ -236,15 +231,15 @@ return view.extend({
let layout = E('div', { 'class': 'cbi-section-node' }); let layout = E('div', { 'class': 'cbi-section-node' });
function layout_append(title, descr, elem) { function layout_append(title, descr, elems) {
descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : ''; descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : '';
let elist; let elist = elems;
if (elem instanceof E) { let elem_list = [ ];
elist = [ elem ]; for (let i = 0; i < elist.length; i++) {
} else { elem_list.push(elist[i]);
elist = elem; elem_list.push(' ');
} }
let vlist = [ E('div', {}, elist ) ]; let vlist = [ E('div', {}, elem_list ) ];
for (let i = 0; i < elist.length; i++) { for (let i = 0; i < elist.length; i++) {
let input = E('input', { let input = E('input', {
'id' : elist[i].id + '_hidden', 'id' : elist[i].id + '_hidden',
@@ -252,9 +247,10 @@ return view.extend({
}); });
vlist.push(input); vlist.push(input);
} }
let elem_name = (elist.length == 1) ? elist[0].id + '_hidden' : null;
layout.append( layout.append(
E('div', { 'class': 'cbi-value' }, [ E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'for': elem.id + '_hidden' || null }, title), E('label', { 'class': 'cbi-value-title', 'for': elem_name }, title),
E('div', { 'class': 'cbi-value-field' }, vlist), E('div', { 'class': 'cbi-value-field' }, vlist),
]) ])
); );
@@ -272,26 +268,32 @@ return view.extend({
btn_enable.onclick = ui.createHandlerFn(this, this.serviceAction, 'enable', 'btn_enable'); btn_enable.onclick = ui.createHandlerFn(this, this.serviceAction, 'enable', 'btn_enable');
let btn_disable = create_btn('btn_disable', btn_style_warning, _('Disable')); let btn_disable = create_btn('btn_disable', btn_style_warning, _('Disable'));
btn_disable.onclick = ui.createHandlerFn(this, this.serviceAction, 'disable', 'btn_disable'); btn_disable.onclick = ui.createHandlerFn(this, this.serviceAction, 'disable', 'btn_disable');
layout_append(_('Service Status'), null, [ btn_enable, btn_disable ] ); layout_append(_('Service autorun control'), null, [ btn_enable, btn_disable ] );
let btn_start = create_btn('btn_start', btn_style_action, _('Start')); let btn_start = create_btn('btn_start', btn_style_action, _('Start'));
btn_start.onclick = ui.createHandlerFn(this, this.serviceAction, 'start', 'btn_start'); btn_start.onclick = ui.createHandlerFn(this, this.serviceActionEx, 'start', 'btn_start');
let btn_restart = create_btn('btn_restart', btn_style_action, _('Restart')); let btn_restart = create_btn('btn_restart', btn_style_action, _('Restart'));
btn_restart.onclick = ui.createHandlerFn(this, this.serviceAction, 'restart', 'btn_restart'); btn_restart.onclick = ui.createHandlerFn(this, this.serviceActionEx, 'restart', 'btn_restart');
let btn_stop = create_btn('btn_stop', btn_style_warning, _('Stop')); let btn_stop = create_btn('btn_stop', btn_style_warning, _('Stop'));
btn_stop.onclick = ui.createHandlerFn(this, this.serviceAction, 'stop', 'btn_stop'); btn_stop.onclick = ui.createHandlerFn(this, this.serviceAction, 'stop', 'btn_stop');
layout_append(_('Service Control'), null, [ btn_start, btn_restart, btn_stop ] ); layout_append(_('Service daemons control'), null, [ btn_start, btn_restart, btn_stop ] );
let btn_update = create_btn('btn_update', btn_style_action, _('Update')); let btn_update = create_btn('btn_update', btn_style_action, _('Update'));
btn_update.onclick = ui.createHandlerFn(this, () => { this.appAction('update', 'btn_update') }); btn_update.onclick = ui.createHandlerFn(this, () => { this.appAction('update', 'btn_update') });
layout_append(_('Update blacklist'), null, btn_update); layout_append(_('Update blacklist'), null, [ btn_update ] );
let btn_destroy = create_btn('btn_destroy', btn_style_negative, _('Shutdown')); let btn_destroy = create_btn('btn_destroy', btn_style_negative, _('Shutdown'));
btn_destroy.onclick = L.bind(this.dialogDestroy, this); btn_destroy.onclick = L.bind(this.dialogDestroy, this);
//layout_append(_('Shutdown'), _('Complete service shutdown'), btn_destroy);
//let elems = [ status_string, btn_start, btn_enable, btn_update, btn_destroy ]; let elems = {
let elems = [ status_string, btn_start, btn_enable, btn_update ]; "status": status_string,
"btn_enable": btn_enable,
"btn_disable": btn_disable,
"btn_start": btn_start,
"btn_restart": btn_restart,
"btn_stop": btn_stop,
"btn_update": btn_update,
};
this.setAppStatus(status_array, elems); this.setAppStatus(status_array, elems);
poll.add(L.bind(this.statusPoll, this)); poll.add(L.bind(this.statusPoll, this));
@@ -309,7 +311,6 @@ return view.extend({
), ),
E('div', { 'class': 'cbi-section fade-in' }, [ E('div', { 'class': 'cbi-section fade-in' }, [
status_string, status_string,
E('hr'),
]), ]),
E('div', { 'class': 'cbi-section fade-in' }, E('div', { 'class': 'cbi-section fade-in' },
layout layout

View File

@@ -37,6 +37,8 @@ document.head.append(E('style', {'type': 'text/css'},
return baseclass.extend({ return baseclass.extend({
appName : 'zapret', appName : 'zapret',
execPath : '/etc/init.d/zapret', execPath : '/etc/init.d/zapret',
syncCfgPath : '/opt/zapret/sync_config.sh',
hostsUserFN : '/opt/zapret/ipset/zapret-hosts-user.txt', hostsUserFN : '/opt/zapret/ipset/zapret-hosts-user.txt',
hostsUserExcludeFN: '/opt/zapret/ipset/zapret-hosts-user-exclude.txt', hostsUserExcludeFN: '/opt/zapret/ipset/zapret-hosts-user-exclude.txt',
iplstExcludeFN : '/opt/zapret/ipset/zapret-ip-exclude.txt', iplstExcludeFN : '/opt/zapret/ipset/zapret-ip-exclude.txt',
@@ -45,13 +47,23 @@ return baseclass.extend({
custFileMax : 4, custFileMax : 4,
custFileTemplate : '/opt/zapret/ipset/cust%s.txt', custFileTemplate : '/opt/zapret/ipset/cust%s.txt',
infoLabelRunning : '<span class="label-status running">' + _('Running') + '</span>',
infoLabelStarting : '<span class="label-status starting">' + _('Starting') + '</span>', infoLabelStarting : '<span class="label-status starting">' + _('Starting') + '</span>',
infoLabelRunning : '<span class="label-status running">' + _('Enabled') + '</span>', infoLabelStopped : '<span class="label-status stopped">' + _('Stopped') + '</span>',
infoLabelUpdating : '<span class="label-status updating">' + _('Updating') + '</span>', infoLabelDisabled : '<span class="label-status stopped">' + _('Disabled') + '</span>',
infoLabelStopped : '<span class="label-status stopped">' + _('Disabled') + '</span>', infoLabelError : '<span class="label-status error">' + _('Error') + '</span>',
infoLabelError : '<span class="label-status error">' + _('Error') + '</span>',
callInitStatus: rpc.declare({ 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', object: 'luci',
method: 'getInitList', method: 'getInitList',
params: [ 'name' ], params: [ 'name' ],
@@ -65,10 +77,10 @@ return baseclass.extend({
expect: { result: false } expect: { result: false }
}), }),
getInitStatus: function(name) { getInitState: function(name) {
return this.callInitStatus(name).then(res => { return this.callInitState(name).then(res => {
if (res) { if (res) {
return res[name].enabled; return res[name].enabled ? true : false;
} else { } else {
throw _('Command failed'); throw _('Command failed');
} }
@@ -92,72 +104,130 @@ return baseclass.extend({
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v; return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
}, },
makeStatusString: function(app_status_code, fwtype, bllist_preset) { get_pid_list: function(proc_list) {
let app_status_label; let plist = [ ];
let spinning = ''; let lines = proc_list.trim().split('\n');
/* for (let i = 0; i < lines.length; i++) {
switch(app_status_code) { let line = lines[i].trim();
case 0: if (line.length > 5) {
app_status_label = this.infoLabelRunning; let word_list = line.split(/\s+/);
break; let pid = word_list[0];
case 2: let isnum = /^\d+$/.test(pid);
app_status_label = this.infoLabelStopped; if (isnum) {
break; plist.push(parseInt(pid));
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 plist;
return `<table class="table"> },
decode_svc_info: function(svc_autorun, svc_info, proc_list, cfg) {
let result = {
"autorun": svc_autorun,
"dmn": {
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') {
let dmn_list = jdata.zapret.instances;
if (typeof(dmn_list) !== 'object') {
return -4;
}
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.working) ? 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.total == 0) {
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"> <tr class="tr">
<td class="td left" style="min-width:33%%"> <td class="td left" ${td_name_style}>
${_('Status')}: ${_('Service autorun status')}:
</td> </td>
<td class="td left%s"> <td class="td left">
%s %s ${svc_autorun}
</td> </td>
</tr> </tr>
<tr class="tr"> <tr class="tr">
<td class="td left"> <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')}: ${_('FW type')}:
</td> </td>
<td class="td left"> <td class="td left">
%s ${fwtype}
</td> </td>
</tr> </tr>
<tr class="tr"> <tr class="tr">
<td class="td left"> <td class="td left" ${td_name_style}>
${_('Blacklist update mode')}: ${_('Blacklist update mode')}:
</td> </td>
<td class="td left"> <td class="td left">
%s ${update_mode}
</td> </td>
</tr> </tr>
</table> <tr class="tr">
`.format( <td class="td left" ${td_name_style}>
spinning, </td>
app_status_label, <td class="td left">
'', </td>
fwtype, </tr>
_('user entries only') </table>`;
); return out;
}, },
fileEditDialog: baseclass.extend({ fileEditDialog: baseclass.extend({

View File

@@ -8,6 +8,7 @@
"/opt/zapret/ipset/*": [ "read" ], "/opt/zapret/ipset/*": [ "read" ],
"/etc/crontabs/root": [ "read" ], "/etc/crontabs/root": [ "read" ],
"/etc/init.d/zapret*": [ "exec" ], "/etc/init.d/zapret*": [ "exec" ],
"/bin/ps*": [ "exec" ],
"/opt/zapret/sync_config.sh*": [ "exec" ] "/opt/zapret/sync_config.sh*": [ "exec" ]
}, },
"uci": [ "zapret", "network" ], "uci": [ "zapret", "network" ],