luci-app-keepalived: Add LuCI for keepalived

LuCI Support for Keepalived

Signed-off-by: Jaymin Patel <jem.patel@gmail.com>
This commit is contained in:
Jaymin Patel 2022-09-08 10:05:13 +05:30
parent 3e9d9a9dbb
commit d1a82d2886
15 changed files with 1376 additions and 0 deletions

View file

@ -0,0 +1,18 @@
#
# Copyright (C) 2022 Jaymin Patel <jem.patel@gmail.com>
#
# This is free software, licensed under the GNU General Public License v2.
include $(TOPDIR)/rules.mk
PKG_LICENSE:=GPL-2.0-or-later
PKG_MAINTAINER:=Jaymin Patel <jem.patel@gmail.com>
LUCI_TITLE:=LuCI support for the Keepalived
LUCI_DEPENDS:=+luci-base +keepalived +keepalived-sync
LUCI_PKGARCH:=all
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,66 @@
'use strict';
'require view';
'require form';
return view.extend({
render: function() {
var m, s, o;
m = new form.Map('keepalived');
s = m.section(form.TypedSection, 'globals', _('Keepalived Global Settings'));
s.anonymous = true;
s.addremove = false;
o = s.option(form.Value, 'router_id', _('Router ID'),
_('String identifying the machine (doesn\'t have to be hostname)'));
o.optional = true;
o.placeholder = 'OpenWrt';
o = s.option(form.Flag, 'linkbeat_use_polling', _('Link Polling'),
_('Poll to detect media link failure using ETHTOOL, MII or ioctl interface otherwise uses netlink interface'));
o.optional = true;
o.default = true;
o = s.option(form.DynamicList, 'notification_email', _('Notification E-Mail'),
_('EMail accounts that will receive the notification mail'));
o.optional = true;
o.placeholder = 'admin@example.com';
o = s.option(form.Value, 'notification_email_from', _('Notification E-Mail From'),
_('Email to use when processing “MAIL FROM:” SMTP command'));
o.optional = true;
o.placeholder = 'admin@example.com';
o = s.option(form.Value, 'smtp_server', _('SMTP Server'),
_('Server to use for sending mail notifications'));
o.optional = true;
o.placeholder = '127.0.0.1 [<PORT>]';
o = s.option(form.Value, 'smtp_connect_timeout', _('SMTP Connect Timeout'),
_('Timeout in seconds for SMTP stream processing'));
o.optional = true;
o.datatype = 'uinteger';
o.placeholder = '30';
o = s.option(form.Value, 'vrrp_mcast_group4', _('VRRP Multicast Group 4'),
_('Multicast Group to use for IPv4 VRRP adverts'));
o.optional = true;
o.datatype = 'ip4addr';
o.placeholder = '224.0.0.18';
o = s.option(form.Value, 'vrrp_mcast_group6', _('VRRP Multicast Group 6'),
_('Multicast Group to use for IPv6 VRRP adverts'));
o.optional = true;
o.datatype = 'ip6addr';
o.placeholder = 'ff02::12';
o = s.option(form.Value, 'vrrp_startup_delay', _('VRRP Startup Delay'),
_('Delay in seconds before VRRP instances start up after'));
o.optional = true;
o.datatype = 'float';
o.placeholder = '5.5';
return m.render();
}
});

View file

@ -0,0 +1,90 @@
'use strict';
'require view';
'require ui';
'require form';
'require uci';
'require tools.widgets as widgets';
return view.extend({
load: function() {
return Promise.all([
uci.load('keepalived'),
]);
},
renderIPAddress: function(m) {
var s, o;
s = m.section(form.GridSection, 'ipaddress', _('IP Addresses'),
_('Addresses would be referenced into Static and Virtual IP Address of VRRP instances'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.rmempty = false;
o.optional = false;
o.placeholder = 'name';
o = s.option(form.Value, 'address', _('Address'),
_('IP Address of the object'));
o.rmempty = false;
o.optional = false;
o.datatype = 'ipaddr';
o.placeholder = '192.168.1.1';
o = s.option(widgets.DeviceSelect, 'device', _('Device'),
_('Device to use to assign the Address'));
o.optional = true;
o.noaliases = true;
o = s.option(form.Value, 'label_suffix', _('Virtual Device Label'),
_('Creates virtual device with Label'));
o.datatype = 'maxlength(4)';
o.optional = true;
o = s.option(form.ListValue, 'scope', _('Scope'),
_('Scope of the Address'));
o.value('site', _('Site'));
o.value('link', _('Link'));
o.value('host', _('Host'));
o.value('nowhere', _('No Where'));
o.value('global', _('Global'));
o.optional = true;
},
renderStaticIPAddress: function(m) {
var s, o;
var ipaddress;
ipaddress = uci.sections('keepalived', 'ipaddress');
if (ipaddress == '') {
ui.addNotification(null, E('p', _('IP Addresses must be configured for Static IP List')));
}
s = m.section(form.GridSection, 'static_ipaddress', _('Static IP Addresses'),
_('Static Addresses are not moved by vrrpd, they stay on the machine.') + '<br/>' +
_('If you already have IPs on your machines and your machines can ping each other, you don\'t need this section'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.DynamicList, 'address', _('IP Address'),
_('List of IP Addresses'));
for (var i = 0; i < ipaddress.length; i++) {
o.value(ipaddress[i]['name']);
}
o.optional = true;
},
render: function() {
var m;
m = new form.Map('keepalived');
this.renderIPAddress(m);
this.renderStaticIPAddress(m);
return m.render();
}
});

View file

@ -0,0 +1,75 @@
'use strict';
'require view';
'require form';
'require uci';
'require rpc';
'require poll';
var callKeepalivedStatus = rpc.declare({
object: 'keepalived',
method: 'dump',
expect: { },
});
return view.extend({
load: function() {
return Promise.all([
uci.load('keepalived'),
]);
},
render: function() {
var table =
E('table', { 'class': 'table lases' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Name')),
E('th', { 'class': 'th' }, _('Interface')),
E('th', { 'class': 'th' }, _('Active State/State')),
E('th', { 'class': 'th' }, _('Probes Sent')),
E('th', { 'class': 'th' }, _('Probes Received')),
E('th', { 'class': 'th' }, _('Last Transition')),
E([])
])
]);
poll.add(function() {
return callKeepalivedStatus().then(function(instancesInfo) {
var targets = Array.isArray(instancesInfo.status) ? instancesInfo.status : [];
var instances = uci.sections('keepalived', 'vrrp_instance');
cbi_update_table(table,
targets.map(function(target) {
var state = (target.stats.become_master - target.stats.release_master) ? 'MASTER' : 'BACKUP';
if (instances != '') {
for (var i = 0; i < instances.length; i++) {
if (instances[i]['name'] == target.data.iname) {
state = state + '/' + instances[i]['state'];
break;
}
}
}
return [
target.data.iname,
target.data.ifp_ifname,
state,
target.stats.advert_sent,
target.stats.advert_rcvd,
new Date(target.data.last_transition * 1000)
];
}),
E('em', _('There are no active instances'))
);
});
});
return E([
E('h3', _('Keepalived Instances Status')),
E('br'),
table
]);
},
handleSave: null,
handleSaveApply:null,
handleReset: null
});

View file

@ -0,0 +1,97 @@
'use strict';
'require view';
'require form';
'require rpc';
return view.extend({
callHostHints: rpc.declare({
object: 'luci-rpc',
method: 'getHostHints',
expect: { '': {} }
}),
load: function() {
return Promise.all([
this.callHostHints(),
]);
},
render: function(data) {
var hosts = data[0];
var m, s, o;
m = new form.Map('keepalived');
s = m.section(form.GridSection, 'peer', _('Peers'),
_('Peers can be referenced into Instances cluster and data/config synchronization'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.optional = false;
o.placeholder = 'name';
o = s.option(form.Value, 'address', _('Peer Address'));
o.optional = false;
o.rmempty = false;
o.datatype = 'ipaddr';
for(var mac in hosts) {
if (hosts[mac]['ipaddrs'] == 'undefined') {
continue;
}
for(var i = 0; i < hosts[mac]['ipaddrs'].length; i++) {
o.value(hosts[mac]['ipaddrs'][i]);
}
}
o = s.option(form.Flag, 'sync', _('Enable Sync'),
_('Auto Synchonize Config/Data files with peer'));
o = s.option(form.ListValue, 'sync_mode', _('Sync Mode'),
_('Current System should act as Sender/Receiver.') + '<br/>' +
_('If peer is backup node, Current system should be sender, If peer is master current system should be receiver'));
o.value('send', _('Sender'));
o.value('receive', _('Receiver'));
o.default = 'send';
o.depends({ 'sync' : '1' });
o = s.option(form.Value, 'ssh_port', _('SSH Port'),
_('If peer runs on non standard ssh port, change to correct ssh port number'));
o.datatype = 'port';
o.default = '22';
o.modalonly = true;
o.depends({ 'sync' : '1', 'sync_mode' : 'send' });
o = s.option(form.Value, 'sync_dir', _('Sync Directory'),
_('Sender will send files to this location of receiver. Must be same on Master/Backup'));
o.default = '/usr/share/keepalived/rsync';
o.optional = false;
o.rmempty = false;
o.modalonly = true;
o.datatype = 'directory';
o.depends({ 'sync' : '1' });
o = s.option(form.FileUpload, 'ssh_key', _('Path to SSH Private Key'),
_('Use SSH key for password less authentication, SSH Key would be used on current system'));
o.root_directory = '/etc/keepalived/keys';
o.enable_upload = true;
o.modalonly = true;
o.datatype = 'file';
o.depends({ 'sync' : '1', 'sync_mode' : 'send' });
o = s.option(form.TextValue, 'ssh_pubkey', _('SSH Public Key'),
_('Authorize ssh public key of peer'));
o.datatype = 'string';
o.modalonly = true;
o.depends({ 'sync' : '1', 'sync_mode' : 'receive' });
o = s.option(form.DynamicList, 'sync_list', _('Sync Files'),
_('Additional files to synchronize, By default it synchronizes sysupgrade backup files'));
o.datatype = 'file';
o.modalonly = true;
o.depends({ 'sync' : '1', 'sync_mode' : 'send' });
return m.render();
}
});

View file

@ -0,0 +1,96 @@
'use strict';
'require view';
'require ui';
'require form';
'require uci';
'require tools.widgets as widgets';
return view.extend({
load: function() {
return Promise.all([
uci.load('keepalived'),
]);
},
renderRoute: function(m) {
var s, o;
s = m.section(form.GridSection, 'route', _('Routes'),
_('Routes would be refereenced into Static and Virtual Routes of VRRP instances'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.optional = false;
o.placeholder = 'name';
o = s.option(widgets.DeviceSelect, 'device', _('Device'),
_('Device to use for Routing'));
o.optional = true;
o.noaliases = true;
o = s.option(form.Value, 'address', _('Target/Destination'),
_('Target IP Address of the Route'));
o.optional = true;
o.datatype = 'ipaddr';
o.placeholder = '192.168.1.1';
o = s.option(form.Value, 'src_addr', _('Source Address'),
_('Source Address of the Route'));
o.optional = true;
o.datatype = 'ipaddr';
o.placeholder = '192.168.1.1';
o = s.option(form.Value, 'gateway', _('Gateway'),
_('Gateway to use for the Route'));
o.optional = true;
o.datatype = 'ipaddr';
o.placeholder = '192.168.1.1';
o = s.option(form.Value, 'table', _('Route Table'),
_('System Route Table'));
o.value('default', _('default'));
o.value('Main', _('Main'));
o.optional = true;
o = s.option(form.Flag, 'blackhole', _('Blackhole'));
o.optional = true;
o.placeholder = 'name';
},
renderStaticRoutes: function(m) {
var s, o;
var route;
route = uci.sections('keepalived', 'route');
if (route == '') {
ui.addNotification(null, E('p', _('Routes must be configured for Static Routes')));
}
s = m.section(form.GridSection, 'static_routes', _('Static Routes'),
_('Static Routes are not moved by vrrpd, they stay on the machine.') + '<br/>' +
_('If you already have routes on your machines and your machines can ping each other, you don\'t need this section'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.DynamicList, 'route', _('Route'),
_('List of Route Object'));
for (var i = 0; i < route.length; i++) {
o.value(route[i]['name']);
}
o.optional = true;
},
render: function() {
var m;
m = new form.Map('keepalived');
this.renderRoute(m);
this.renderStaticRoutes(m);
return m.render();
}
});

View file

@ -0,0 +1,106 @@
'use strict';
'require view';
'require ui';
'require form';
'require uci';
return view.extend({
load: function() {
return Promise.all([
uci.load('keepalived'),
]);
},
renderTrackScript: function(m) {
var s, o;
var vrrp_scripts;
vrrp_scripts = uci.sections('keepalived', 'vrrp_script');
if (vrrp_scripts == '') {
ui.addNotification(null, E('p', _('VRRP Scripts must be configured for Track Scripts')));
}
s = m.section(form.GridSection, 'track_script', _('Track Script'),
_('Tracking scripts would be referenced from VRRP instances'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.optional = false;
o.rmempty = false;
o = s.option(form.ListValue, 'value', _('VRRP Script'));
o.optional = false;
o.rmempty = false;
if (vrrp_scripts != '') {
for (i = 0; i < vrrp_scripts.length; i++) {
o.value(vrrp_scripts[i]['name']);
}
}
o = s.option(form.Value, 'weight', _('Weight'));
o.optional = true;
o.datatype = 'and(integer, range(-253, 253))';
o = s.option(form.ListValue, 'direction', _('Direction'));
o.optional = true;
o.default = '';
o.value('reverse', _('Reverse'));
o.value('noreverse', _('No Reverse'));
},
renderVRRPScript: function(m) {
var s, o;
s = m.section(form.GridSection, 'vrrp_script', _('VRRP Script'),
_('Adds a script to be executed periodically. Its exit code will be recorded for all VRRP instances and sync groups which are monitoring it'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.optional = true;
o.placeholder = 'name';
o = s.option(form.FileUpload, 'script', _('Script'),
_('Path of the script to execute'));
o.root_directory = '/etc/keepalived/scripts';
o.enable_upload = true;
o.optional = true;
o.datatype = 'file';
o = s.option(form.Value, 'interval', _('Interval'),
_('Seconds between script invocations'));
o.optional = true;
o.datatype = 'uinteger';
o.default = 60;
o = s.option(form.Value, 'weight', _('Weight'),
_('Adjust script execution priority'));
o.optional = true;
o.datatype = 'and(integer, range(-253, 253))';
o = s.option(form.Value, 'rise', _('Rise'),
_('Required number of successes for OK transition'));
o.optional = true;
o.datatype = 'uinteger';
o = s.option(form.Value, 'fail', _('Fail'),
_('Required number of successes for KO transition'));
o.optional = true;
o.datatype = 'uinteger';
},
render: function() {
var m;
m = new form.Map('keepalived');
this.renderVRRPScript(m);
this.renderTrackScript(m);
return m.render();
}
});

View file

@ -0,0 +1,204 @@
'use strict';
'require view';
'require form';
'require uci';
return view.extend({
load: function() {
return Promise.all([
uci.load('keepalived'),
]);
},
renderVirtualServer: function(m) {
var s, o;
var real_servers;
s = m.section(form.GridSection, 'virtual_server', _('Virtual Server'),
_('A virtual server is a service configured to listen on a specific virtual IP.') + '<br/>' +
_('A VIP address migrates from one LVS router to the other during a failover,') +
_('thus maintaining a presence at that IP address'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
s.tab('general', _('General'));
s.tab('advanced', _('Advanced'));
o = s.taboption('general', form.Flag, 'enabled', _('Enable'));
o.optional = true;
o.placeholder = 'name';
o = s.taboption('general', form.Value, 'ipaddr', _('Address'),
_('Address of the Server'));
o.datatype = 'ipaddr';
o = s.taboption('general', form.ListValue, 'protocol', _('Protocol'));
o.value('TCP');
o.value('UDP');
o.default = 'TCP';
o.modalonly = true;
o = s.taboption('general', form.Value, 'port', _('Port'),
_('Server Port'));
o.rmempty = false;
o.optional = false;
o.datatype = 'port';
o = s.taboption('general', form.Value, 'fwmark', _('Mark'),
_('Firewall fwmark. Use Virtual server from FWMARK'));
o.datatype = 'hexstring';
real_servers = uci.sections('keepalived', 'real_server');
o = s.taboption('general', form.DynamicList, 'real_server', _('Real Server'));
if (real_servers != '') {
for (i = 0; i < real_servers.length; i++) {
o.value(real_servers[i]['name']);
}
}
o.optional = false;
o = s.taboption('general', form.Value, 'virtualhost', _('Virtual Host'),
_('HTTP virtualhost to use for HTTP_GET | SSL_GET'));
o.datatype = 'hostname';
o.modalonly = true;
o = s.taboption('general', form.ListValue, 'lb_kind', _('Forwarding Method'));
o.value('NAT');
o.value('DR');
o.value('TUN');
o.default = 'NAT';
o = s.taboption('advanced', form.Value, 'delay_loop', _('Delay Loop'),
_('Interval between checks in seconds'));
o.optional = false;
o.datatype = 'uinteger';
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'lb_algo', _('Scheduler Algorigthm'));
o.value('rr', _('Round-Robin'));
o.value('wrr', _('Weighted Round-Robin'));
o.value('lc', _('Least-Connection'));
o.value('wlc', _('Weighted Least-Connection'));
o.default = 'rr';
o = s.taboption('advanced', form.Value, 'persistence_timeout', _('Persist Timeout'),
_('Timeout value for persistent connections'));
o.datatype = 'uinteger';
o.default = 50;
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'persistence_granularity', _('Persist Granularity'),
_('Granularity mask for persistent connections'));
o.datatype = 'ipaddr';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'sorry_server_ip', _('Sorry Server Address'),
_('Server to be added to the pool if all real servers are down'));
o.optional = false;
o.datatype = 'ipaddr';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'sorry_server_port', _('Sorry Server Port'));
o.optional = false;
o.datatype = 'port';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'rise', _('Rise'),
_('Required number of successes for OK transition'));
o.optional = true;
o.datatype = 'uinteger';
o.modalonly = true;
o = s.taboption('advanced', form.Value, 'fail', _('Fail'),
_('Required number of successes for KO transition'));
o.optional = true;
o.datatype = 'uinteger';
o.modalonly = true;
},
renderRealServer: function(m) {
var s, o;
var urls;
s = m.section(form.GridSection, 'real_server', _('Real Servers'),
_('Real Server to redirect all request'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.rmempty = false;
o.optional = false;
o.placeholder = 'name';
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.default = true;
o = s.option(form.Value, 'ipaddr', _('Address'),
_('Address of the Server'));
o.rmempty = false;
o.optional = false;
o.datatype = 'ipaddr';
o = s.option(form.Value, 'port', _('Port'),
_('Server Port'));
o.rmempty = false;
o.optional = false;
o.datatype = 'port';
o = s.option(form.Value, 'weight', _('Weight'),
_('Relative weight to use'));
o.rmempty = false;
o.optional = false;
o.placeholder = 1;
o.datatype = 'uinteger';
o = s.option(form.ListValue, 'check', _('Check'),
_('Healthcheckers. Can be multiple of each type'));
o.value('HTTP_GET');
o.value('SSL_GET');
o.value('TCP_CHECK');
o.value('MISC_CHECK');
o = s.option(form.Value, 'connect_timeout', _('Connect Timeout'));
o.datatype = 'uinteger';
o.depends('check', 'TCP_CHECK');
o = s.option(form.Value, 'connect_port', _('Port'),
_('Port to connect to'));
o.datatype = 'port';
o.depends('check', 'TCP_CHECK');
o = s.option(form.Value, 'misc_path', _('User Check Script'));
o.datatype = 'file';
o.depends('check', 'MISC_CHECK');
urls = uci.sections('keepalived', 'url');
o = s.option(form.DynamicList, 'url', _('URLs'));
if (urls != '') {
for (var i = 0; i < urls.length; i++) {
o.value(urls[i].name);
}
}
o.depends('check', 'HTTP_GET');
o.depends('check', 'SSL_GET');
o = s.option(form.Value, 'retry', _('Retry'));
o.datatype = 'uinteger';
o = s.option(form.Value, 'delay_before_retry', _('Delay Before Retry'));
o.datatype = 'uinteger';
},
render: function() {
var m;
m = new form.Map('keepalived');
this.renderVirtualServer(m);
this.renderRealServer(m);
return m.render();
}
});

View file

@ -0,0 +1,36 @@
'use strict';
'require view';
'require form';
'require tools.widgets as widgets';
'require uci';
return view.extend({
render: function() {
var m, s, o;
m = new form.Map('keepalived');
s = m.section(form.GridSection, 'track_interface', _('Track Interface'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.rmempty = false;
o.optional = false;
o = s.option(widgets.DeviceSelect, 'value', _('Device'),
_('Device to track'));
o.noaliases = true;
o.rmempty = false;
o.optional = false;
o = s.option(form.Value, 'weight', _('Weight'),
_('When a weight is specified, instead of setting the vrrp_instance to the FAULT state in case of failure, ') +
_('its priority will be increased or decreased by the weight when the interface is up or down'));
o.optional = false;
o.datatype = 'uinteger';
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('keepalived');
s = m.section(form.GridSection, 'url', _('URLs'),
_('URLs can be referenced into Real Servers to test'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.optional = false;
o = s.option(form.Value, 'path', _('URL Path'),
_('URL path, i.e path /, or path /mrtg2/'));
o.optional = false;
o = s.option(form.Value, 'digest', _('Digest'),
_('Digest computed with genhash'));
o.datatype = 'length(32)';
return m.render();
}
});

View file

@ -0,0 +1,310 @@
'use strict';
'require view';
'require form';
'require uci';
'require network';
'require tools.widgets as widgets';
return view.extend({
load: function() {
return Promise.all([
network.getDevices(),
uci.load('keepalived'),
]);
},
renderGeneralTab: function(s) {
var o, ipaddress;
o = s.taboption('general',form.Value, 'name', _('Name'));
o.rmempty = false;
o.optional = false;
o = s.taboption('general', form.ListValue, 'state', _('State'),
_('Initial State. As soon as the other machine(s) come up,') +
_('an election will be held and the machine with the highest "priority" will become MASTER.'));
o.value('MASTER', _('Master'));
o.value('BACKUP', _('Backup'));
o.optional = false;
o.rmempty = false;
o = s.taboption('general', widgets.DeviceSelect, 'interface', _('Interface'),
_('Interface for inside_network, bound by VRRP'));
o.noaliases = true;
o.noinactive = true;
o.optional = false;
o.rmempty = false;
o = s.taboption('general', form.Value, 'virtual_router_id', _('Virtual Router Id'),
_('Differentiate multiple instances of vrrpd, running on the same NIC'));
o.datatype = 'range(1-255)';
o.optional = false;
o.rmempty = false;
o = s.taboption('general', form.Value, 'priority', _('Priority'),
_('A server with a higher priority becomes a MASTER'));
o.datatype = 'uinteger';
o.optional = false;
o.rmempty = false;
o = s.taboption('general', form.ListValue, 'advert_int', _('Interval'),
_('VRRP Advert interval in seconds'));
o.datatype = 'float';
o.default = '1';
o.rmempty = false;
o.optional = false;
o.value('1');
o.value('3');
o.value('5');
o.value('10');
o.value('30');
o.value('60');
o = s.taboption('general', form.Flag, 'nopreempt', _('Disable Preempt'),
_('Allows the lower priority machine to maintain the master role,') +
_('even when a higher priority machine comes back online.') + ' ' +
_('For this to work, the initial state of this entry must be BACKUP.'));
o.default = false;
o.rmempty = false;
ipaddress = uci.sections('keepalived', 'ipaddress');
o = s.taboption('general', form.DynamicList, 'virtual_ipaddress', _('Virtual IP Address'),
_('Addresses add|del on change to MASTER, to BACKUP.') + ' ' +
_('With the same entries on other machines, the opposite transition will be occurring.'));
if (ipaddress != '') {
for (var i = 0; i < ipaddress.length; i++) {
o.value(ipaddress[i]['name']);
}
}
o.rmempty = false;
o.optional = false;
},
renderPeerTab: function(s, netDevs) {
var o;
o = s.taboption('peer', form.ListValue, 'unicast_src_ip', _('Unicast Source IP'),
_('Default IP for binding vrrpd is the primary IP on interface'));
o.datatype = 'ipaddr';
o.optional = true;
o.modalonly = true;
for (var i = 0; i < netDevs.length; i++) {
var addrs = netDevs[i].getIPAddrs();
for (var j = 0; j < addrs.length; j++) {
o.value(addrs[j].split('/')[0]);
}
}
var peers = uci.sections('keepalived', 'peer');
o = s.taboption('peer', form.DynamicList, 'unicast_peer', _('Peer'),
_('Do not send VRRP adverts over VRRP multicast group.') + ' ' +
_('Instead it sends adverts to the following list of ip addresses using unicast design fashion'));
if (peers != '') {
for (var i = 0; i < peers.length; i++) {
o.value(peers[i]['name']);
}
}
o = s.taboption('peer', form.Value, 'mcast_src_ip', _('Multicast Source IP'),
_('If you want to hide location of vrrpd, use this IP for multicast vrrp packets'));
o.datatype = 'ipaddr';
o.optional = true;
o.modalonly = true;
o.depends({ 'unicast_peer' : '' });
o = s.taboption('peer', form.ListValue, 'auth_type', _('HA Authentication Type'));
o.value('PASS', _('Simple Password'));
o.value('AH', _('IPSec'));
o = s.taboption('peer', form.Value, 'auth_pass', _('Password'),
_('Password for accessing vrrpd, should be the same on all machines'));
o.datatype = 'maxlength(8)';
o.password = true;
o.modalonly = true;
o.depends({ 'auth_type' : 'PASS' });
},
renderGARPTab: function(s) {
var o;
o = s.taboption('garp', form.ListValue, 'garp_master_delay', _('GARP Delay'),
_('Gratuitous Master Delay in seconds'));
o.datatype = 'uinteger';
o.modalonly = true;
o.value('1');
o.value('3');
o.value('5');
o.value('10');
o.value('30');
o.value('60');
o = s.taboption('garp', form.ListValue, 'garp_master_repeat', _('GARP Repeat'),
_('Gratuitous Master Repeat in seconds'));
o.datatype = 'uinteger';
o.modalonly = true;
o.value('1');
o.value('3');
o.value('5');
o.value('10');
o.value('30');
o.value('60');
o = s.taboption('garp', form.ListValue, 'garp_master_refresh', _('GARP Refresh'),
_('Gratuitous Master Refresh in seconds'));
o.datatype = 'uinteger';
o.modalonly = true;
o.value('1');
o.value('3');
o.value('5');
o.value('10');
o.value('30');
o.value('60');
o = s.taboption('garp', form.ListValue, 'garp_master_refresh_repeat', _('GARP Refresh Repeat'),
_('Gratuitous Master Refresh Repeat in seconds'));
o.datatype = 'uinteger';
o.modalonly = true;
o.value('1');
o.value('3');
o.value('5');
o.value('10');
o.value('30');
o.value('60');
},
renderAdvancedTab: function(s) {
var o;
o = s.taboption('advanced', form.Value, 'use_vmac', _('Use VMAC'),
_('Use VRRP Virtual MAC'));
o.optional = true;
o.placeholder = '[<VMAC_INTERFACE_NAME>] [MAC_ADDRESS]';
o.modalonly = true;
o = s.taboption('advanced', form.Flag, 'vmac_xmit_base', _('Use VMAC Base'),
_('Send/Recv VRRP messages from base interface instead of VMAC interfac'));
o.default = false;
o.optional = true;
o.modalonly = true;
o = s.taboption('advanced', form.Flag, 'native_ipv6', _('Use IPV6'),
_('Force instance to use IPv6'));
o.default = false;
o.optional = true;
o.modalonly = true;
o = s.taboption('advanced', form.Flag, 'dont_track_primary', _('Disable Primary Tracking'),
_('Ignore VRRP interface faults'));
o.default = false;
o.optional = true;
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'version', _('Version'),
_('VRRP version to run on interface'));
o.value('', _('None'));
o.value('2', _('2'));
o.value('3', _('3'));
o.default = '';
o.modalonly = true;
o = s.taboption('advanced', form.Flag, 'accept', _('Accept'),
_('Accept packets to non address-owner'));
o.default = false;
o.optional = true;
o = s.taboption('advanced', form.Value, 'preempt_delay', _('Preempt Delay'),
_('Time in seconds to delay preempting compared'));
o.datatype = 'float';
o.placeholder = '300';
o.modalonly = true;
o = s.taboption('advanced', form.ListValue, 'preempt_delay', _('Debug'),
_('Debug Level'));
o.default = '0';
o.value('0');
o.value('1');
o.value('2');
o.value('3');
o.value('4');
o.modalonly = true;
o = s.taboption('advanced', form.Flag, 'smtp_alert', _('Email Alert'),
_('Send SMTP alerts'));
o.default = false;
o.modalonly = true;
},
renderTrackingTab: function(s) {
var o;
var ipaddress, routes, interfaces, scripts;
ipaddress = uci.sections('keepalived', 'ipaddress');
routes = uci.sections('keepalived', 'route');
interfaces = uci.sections('keepalived', 'track_interface');
scripts = uci.sections('keepalived', 'track_script');
o = s.taboption('tracking', form.DynamicList, 'virtual_ipaddress_excluded', _('Exclude Virtual IP Address'),
_('VRRP IP excluded from VRRP. For cases with large numbers (eg 200) of IPs on the same interface.') + ' ' +
_('To decrease the number of packets sent in adverts, you can exclude most IPs from adverts.'));
o.modalonly = true;
if (ipaddress != '') {
for (var i = 0; i < ipaddress.length; i++) {
o.value(ipaddress[i]['name']);
}
}
o = s.taboption('tracking', form.DynamicList, 'virtual_routes', _('Virtual Routes'),
_('Routes add|del when changing to MASTER, to BACKUP'));
o.modalonly = true;
if (routes != '') {
for (var i = 0; i < routes.length; i++) {
o.value(routes[i]['name']);
}
}
o = s.taboption('tracking', form.DynamicList, 'track_interface', _('Track Interfaces'),
_('Go to FAULT state if any of these go down'));
o.modalonly = true;
if (interfaces != '') {
for (var i = 0; i < interfaces.length; i++) {
o.value(interfaces[i]['name']);
}
}
o = s.taboption('tracking', form.DynamicList, 'track_script', _('Track Script'),
_('Go to FAULT state if any of these go down, if unweighted'));
o.modalonly = true;
if (scripts != '') {
for (var i = 0; i < scripts.length; i++) {
o.value(scripts[i]['name']);
}
}
},
render: function(data) {
var netDevs = data[0];
var m, s, o;
m = new form.Map('keepalived');
s = m.section(form.GridSection, 'vrrp_instance', _('VRRP Instance'),
_('Define an individual instance of the VRRP protocol running on an interface'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.tab('general', _('General'));
o = s.tab('peer', _('Peer'));
o = s.tab('tracking', _('Tracking'));
o = s.tab('garp', _('GARP'));
o = s.tab('advanced', _('Advanced'));
this.renderGeneralTab(s);
this.renderPeerTab(s, netDevs);
this.renderTrackingTab(s);
this.renderGARPTab(s);
this.renderAdvancedTab(s);
return m.render();
}
});

View file

@ -0,0 +1,57 @@
'use strict';
'require view';
'require ui';
'require form';
'require uci';
return view.extend({
load: function() {
return Promise.all([
uci.load('keepalived'),
]);
},
render: function(data) {
var m, s, o;
var instances;
instances = uci.sections('keepalived', 'vrrp_instance');
if (instances == '' || instances.length < 1) {
ui.addNotification(null, E('p', _('Instances must be configured for VRRP Groups')));
}
m = new form.Map('keepalived');
s = m.section(form.GridSection, 'vrrp_sync_group', _('VRRP synchronization group'),
_('VRRP Sync Group is an extension to VRRP protocol.') + '<br/>' +
_('The main goal is to define a bundle of VRRP instance to get synchronized together') + '<br/>' +
_('so that transition of one instance will be reflected to others group members'));
s.anonymous = true;
s.addremove = true;
s.nodescriptions = true;
o = s.option(form.Value, 'name', _('Name'));
o.rmempty = false;
o.optional = false;
o.placeholder = 'name';
o = s.option(form.DynamicList, 'group', _('Instance Group'));
o.rmempty = false;
o.optional = false;
for (var i = 0; i < instances.length; i++) {
o.value(instances[i]['name']);
}
o = s.option(form.Flag, 'smtp_alert', _('Email Notification'),
_('Send email notification during state transition'));
o.optional = true;
o.default = false;
o = s.option(form.Flag, 'global_tracking', _('Global Tracking'),
_('Track interfaces, scripts and files'));
o.optional = true;
o.default = false;
return m.render();
}
});

View file

@ -0,0 +1,65 @@
'use strict';
'require baseclass';
'require uci';
'require rpc';
var callKeepalivedStatus = rpc.declare({
object: 'keepalived',
method: 'dump',
expect: { },
});
return baseclass.extend({
title: _('Keepalived Instances'),
load: function() {
return Promise.all([
callKeepalivedStatus(),
uci.load('keepalived'),
]);
},
render: function(data) {
var targets = (data[0].status) ? data[0].status : [];
var instances = uci.sections('keepalived', 'vrrp_instance');
var table =
E('table', { 'class': 'table lases' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Name')),
E('th', { 'class': 'th' }, _('Interface')),
E('th', { 'class': 'th' }, _('Active State/State')),
E('th', { 'class': 'th' }, _('Probes Sent')),
E('th', { 'class': 'th' }, _('Probes Received')),
E('th', { 'class': 'th' }, _('Last Transition')),
E([])
])
]);
cbi_update_table(table,
targets.map(function(target) {
var state = (target.stats.become_master - target.stats.release_master) ? 'MASTER' : 'BACKUP';
if (instances != '') {
for (var i = 0; i < instances.length; i++) {
if (instances[i]['name'] == target.data.iname) {
state = state + '/' + instances[i]['state'];
break;
}
}
}
return [
target.data.iname,
target.data.ifp_ifname,
state,
target.stats.advert_sent,
target.stats.advert_rcvd,
new Date(target.data.last_transition * 1000)
];
}, this), E('em', _('There are no active instances')));
return E([
table
]);
},
});

View file

@ -0,0 +1,109 @@
{
"admin/services/keepalived": {
"title": "Keepalived",
"order": 1,
"action": {
"type": "alias",
"path": "admin/services/keepalived/overview"
}
},
"admin/services/keepalived/overview": {
"title": "Overview",
"order": 10,
"action": {
"type": "view",
"path": "keepalived/overview"
}
},
"admin/services/keepalived/globals": {
"title": "Globals",
"order": 20,
"action": {
"type": "view",
"path": "keepalived/globals"
}
},
"admin/services/keepalived/ipaddress": {
"title": "IP Address",
"order": 30,
"action": {
"type": "view",
"path": "keepalived/ipaddress"
}
},
"admin/services/keepalived/route": {
"title": "Route",
"order": 40,
"action": {
"type": "view",
"path": "keepalived/route"
}
},
"admin/services/keepalived/url": {
"title": "URLs",
"order": 50,
"action": {
"type": "view",
"path": "keepalived/url"
}
},
"admin/services/keepalived/script": {
"title": "Scripts",
"order": 80,
"action": {
"type": "view",
"path": "keepalived/script"
}
},
"admin/services/keepalived/track_interface": {
"title": "Interfaces",
"order": 90,
"action": {
"type": "view",
"path": "keepalived/track_interface"
}
},
"admin/services/keepalived/peers": {
"title": "Peers",
"order": 110,
"action": {
"type": "view",
"path": "keepalived/peers"
}
},
"admin/services/keepalived/vrrp_instance": {
"title": "Instance",
"order": 110,
"action": {
"type": "view",
"path": "keepalived/vrrp_instance"
}
},
"admin/services/keepalived/servers": {
"title": "Servers",
"order": 120,
"action": {
"type": "view",
"path": "keepalived/servers"
}
},
"admin/services/keepalived/vrrp_sync_group": {
"title": "Sync Group",
"order": 140,
"action": {
"type": "view",
"path": "keepalived/vrrp_sync_group"
}
}
}

View file

@ -0,0 +1,17 @@
{
"luci-app-keepalived" : {
"description" : "Grant access to LuCI app keepalived",
"read" : {
"ubus" : {
"keepalived" : [ "*" ]
},
"uci": [ "keepalived" ]
},
"write" : {
"uci": [ "keepalived" ],
"file" : {
"/etc/keepalived/keys/*" : [ "write" ]
}
}
}
}