luci-app-apinger: Add LuCI for Apinger

LuCI Support for Apinger

Signed-off-by: Jaymin Patel <jem.patel@gmail.com>
This commit is contained in:
Jaymin Patel 2022-07-16 18:42:47 +05:30
parent b0b9a34f8b
commit 6c151fcddb
10 changed files with 433 additions and 0 deletions

View file

@ -0,0 +1,19 @@
#
# Copyright (C) 2022 Jaymin Patel <jem.patel@gmail.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for the Apinger
LUCI_DEPENDS:=+apinger +apinger-rrd
LUCI_PKGARCH:=all
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Jaymin Patel <jem.patel@gmail.com>
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,30 @@
'use strict';
'require view';
'require form';
return view.extend({
render: function() {
var m, s, o;
m = new form.Map('apinger', _('Apinger - Delay Alarms'),
('This alarm will be fired when target responses are delayed more than "Delay High"') + '<br />' +
_('This alarm will be canceled, when the delay drops below "Delay Low"') + '<br />');
s = m.section(form.GridSection, 'alarm_delay');
s.anonymous = false;
s.addremove = true;
s.addbtntitle = _('Add Delay/Latency Alarm');
o = s.option(form.Value, 'delay_low', _('Delay Low (ms)'));
o.datatype = 'range(1-500)';
o.default = '30';
o.placeholder = '30';
o = s.option(form.Value, 'delay_high', _('Delay High (ms)'));
o.datatype = 'range(1-500)';
o.default = '50';
o.placeholder = '50';
return m.render();
},
});

View file

@ -0,0 +1,24 @@
'use strict';
'require view';
'require form';
return view.extend({
render: function() {
var m, s, o;
m = new form.Map('apinger', _('Apinger - Down Alarm'),
_('This alarm will be fired when target does not respond for "Time"'));
s = m.section(form.GridSection, 'alarm_down');
s.anonymous = false;
s.addremove = true;
s.addbtntitle = _('Add Down Alarm');
o = s.option(form.Value, 'time', _('Time (s)'));
o.datatype = 'range(1-30)';
o.default = '1';
o.placeholder = '1';
return m.render();
},
});

View file

@ -0,0 +1,30 @@
'use strict';
'require view';
'require form';
return view.extend({
render: function() {
var m, s, o;
m = new form.Map('apinger', _('Apinger - Loss Alarms'),
_('This alarm will be fired when packet loss goes over "Loss High"') + '<br />' +
_('This alarm will be canceled, when the loss drops below "Loss Low"'));
s = m.section(form.GridSection, 'alarm_loss');
s.anonymous = false;
s.addremove = true;
s.addbtntitle = _('Add Loss Alarm');
o = s.option(form.Value, 'percent_low', _('Loss Low (%)'));
o.datatype = 'range(1-100)';
o.default = '10';
o.placeholder = '10';
o = s.option(form.Value, 'percent_high', _('Loss High (%)'));
o.datatype = 'range(1-100)';
o.default = '20';
o.placeholder = '20';
return m.render();
},
});

View file

@ -0,0 +1,61 @@
'use strict';
'require view';
'require uci';
'require rpc';
'require fs';
'require ui';
return view.extend({
callServiceList: rpc.declare({
object: 'service',
method: 'list',
params: [ 'name' ],
expect: { 'apinger': {} }
}),
callApingerUpdateGraphs: rpc.declare({
object: 'apinger',
method: 'update_graphs',
expect: { '': {} }
}),
load: function() {
return Promise.all([
this.callServiceList('apinger'),
this.callApingerUpdateGraphs(),
]);
},
render: function(res) {
var running = Object.keys(res[0].instances || {}).length > 0;
var script = res[1]['rrdcgi'];
if (!running) {
return ui.addNotification(null, E('h3', _('Service is not running'), 'danger'));
}
return fs.stat(script).then(function(res) {
if ((res.type == "file") && (res.size > 100)) {
return E([
E('h3', _('Apinger Targets RRD Graph')),
E('br'),
E('div', [
E('iframe', {
src: script.replace(/^\/www/g, ''),
scrolling: 'yes',
style : 'width: 85vw; height: 100vh; border: none;'
})
])
]);
} else {
return ui.addNotification(null, E('h3', _('No data available'), 'danger'));
}
}).catch(function(err) {
return ui.addNotification(null, E('h3', _('No access to server file'), 'danger'));
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View file

@ -0,0 +1,31 @@
'use strict';
'require view';
'require form';
return view.extend({
render: function() {
var m, s, o;
m = new form.Map('apinger', _('Apinger - Interfaces'),
_('Names must match the interface name found in /etc/config/network.'));
s = m.section(form.GridSection, 'interface');
s.anonymous = false;
s.addremove = true;
s.addbtntitle = _('Add Interface Instance');
o = s.option(form.Flag, 'debug', _('Debug'));
o.datatype = 'boolean';
o.default = false;
o = s.option(form.Value, 'status_interval', _('Status Update Interval'));
o.datatype = 'range(1-60)';
o.default = '5';
o = s.option(form.Value, 'rrd_interval', _('RRD Collection Interval'));
o.datatype = 'range(15-60)';
o.default = '30';
return m.render();
},
});

View file

@ -0,0 +1,66 @@
'use strict';
'require view';
'require rpc';
'require form';
'require poll';
var callApingerStatus = rpc.declare({
object: 'apinger',
method: 'status',
expect: { },
});
return view.extend({
render: function() {
var table =
E('table', { 'class': 'table lases' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Interface')),
E('th', { 'class': 'th' }, _('Target')),
E('th', { 'class': 'th' }, _('Source IP')),
E('th', { 'class': 'th' }, _('Address')),
E('th', { 'class': 'th' }, _('Sent')),
E('th', { 'class': 'th' }, _('Received')),
E('th', { 'class': 'th' }, _('Latency')),
E('th', { 'class': 'th' }, _('Loss')),
E('th', { 'class': 'th' }, _('Active Alarms')),
E('th', { 'class': 'th' }, _('Time')),
E([])
])
]);
poll.add(function() {
return callApingerStatus().then(function(targetInfo) {
var targets = Array.isArray(targetInfo.targets) ? targetInfo.targets : [];
cbi_update_table(table,
targets.map(function(target) {
return [
target.interface,
target.target,
target.srcip,
target.address,
target.sent,
target.received,
target.latency,
target.loss,
target.alarm,
new Date(target.timestamp * 1000),
];
}),
E('em', _('There are no active targets'))
);
});
});
return E([
E('h3', _('Apinger Targets')),
E('br'),
table
]);
},
handleSave: null,
handleSaveApply:null,
handleReset: null
});

View file

@ -0,0 +1,80 @@
'use strict';
'require view';
'require form';
'require uci';
return view.extend({
load: function() {
return Promise.all([
uci.load('apinger'),
])
},
render: function(data) {
var m, s, o;
var a_ifaces, a_down, a_delay, a_loss;
a_ifaces = uci.sections('apinger', 'interface');
a_down = uci.sections('apinger', 'alarm_down');
a_delay = uci.sections('apinger', 'alarm_delay');
a_loss = uci.sections('apinger', 'alarm_loss');
m = new form.Map('apinger', _('Apinger - Targets'),
_('Interface: Interface to use to track target') + '<br />' +
_('Address: Target address to be tracked') + '<br />' +
_('Ping Interval: How often the probe should be sent') + '<br />' +
_('Average Delay: How many replies should be used to compute average delay') + '<br />' +
_('Average Loss: How many probes should be used to compute average loss') + '<br />' +
_('Average Delay and Loss: The delay (in samples) after which loss is computed, without this delays larger than interval would be treated as loss') +
'<br />');
s = m.section(form.GridSection, 'target');
s.anonymous = false;
s.addremove = true;
s.addbtntitle = _('Add Target');
o = s.option(form.ListValue, 'interface', _('Interface'));
for (var i = 0; i < a_ifaces.length; i++) {
o.value(a_ifaces[i]['.name']);
}
o = s.option(form.Value, 'address', _('Address'));
o.datatype = 'ip4addr';
o = s.option(form.Value, 'probe_interval', _('Ping Interval'));
o.datatype = 'integer';
o= s.option(form.Value, 'avg_delay_samples', _('Average Delay'));
o.datatype = 'integer';
o = s.option(form.Value, 'avg_loss_samples', _('Average Loss'));
o.datatype = 'integer';
o = s.option(form.Value, 'avg_loss_delay_samples', _('Average Loss/Delay'));
o.datatype = 'integer';
o = s.option(form.Flag, 'rrd', _('Generate RRD Graphs'));
o.datatype = 'boolean';
o.default = false;
o = s.option(form.ListValue, 'alarm_down', _('Down Alarm'));
for (var i = 0; i < a_down.length; i++) {
o.value(a_down[i]['.name']);
}
o.optional = true;
o = s.option(form.ListValue, 'alarm_delay', _('Delay Alarm'));
for (var i = 0; i < a_delay.length; i++) {
o.value(a_delay[i]['.name']);
}
o.optional = true;
o = s.option(form.ListValue, 'alarm_loss', _('Loss Alarm'));
for (var i = 0; i < a_loss.length; i++) {
o.value(a_loss[i]['.name']);
}
o.optional = true;
return m.render();
},
});

View file

@ -0,0 +1,73 @@
{
"admin/services/apinger": {
"title": "Apinger",
"order": 90,
"action": {
"type": "alias",
"path": "admin/services/apinger/overview"
}
},
"admin/services/apinger/overview": {
"title": "Overview",
"order": 10,
"action": {
"type": "view",
"path": "apinger/overview"
}
},
"admin/services/apinger/graphs": {
"title": "Graphs",
"order": 11,
"action": {
"type": "view",
"path": "apinger/graphs"
}
},
"admin/services/apinger/interfaces": {
"title": "Interfaces",
"order": 19,
"action": {
"type": "view",
"path": "apinger/interfaces"
}
},
"admin/services/apinger/alarm_down": {
"title": "Alarm Down",
"order": 20,
"action": {
"type": "view",
"path": "apinger/alarm_down"
}
},
"admin/services/apinger/alarm_delay": {
"title": "Alarm Delay",
"order": 30,
"action": {
"type": "view",
"path": "apinger/alarm_delay"
}
},
"admin/services/apinger/alarm_loss": {
"title": "Alarm loss",
"order": 40,
"action": {
"type": "view",
"path": "apinger/alarm_loss"
}
},
"admin/services/apinger/targets": {
"title": "Targets",
"order": 90,
"action": {
"type": "view",
"path": "apinger/targets"
}
}
}

View file

@ -0,0 +1,19 @@
{
"luci-app-apinger" : {
"description" : "Grant access to LuCI app Apinger",
"read" : {
"ubus" : {
"apinger" : [ "*" ],
"file": [ "stat" ],
"service": [ "list" ]
},
"uci": [ "apinger" ]
},
"write" : {
"ubus" : {
"apinger" : [ "*" ]
},
"uci": [ "apinger" ]
}
}
}