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:
parent
3e9d9a9dbb
commit
d1a82d2886
15 changed files with 1376 additions and 0 deletions
18
applications/luci-app-keepalived/Makefile
Normal file
18
applications/luci-app-keepalived/Makefile
Normal 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
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
|
@ -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
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
});
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue