summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--applications/luci-app-keepalived/Makefile18
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/globals.js66
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/ipaddress.js90
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/overview.js75
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/peers.js97
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/route.js96
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/script.js106
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/servers.js204
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/track_interface.js36
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/url.js30
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_instance.js310
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_sync_group.js57
-rw-r--r--applications/luci-app-keepalived/htdocs/luci-static/resources/view/status/include/35_keepalived.js65
-rw-r--r--applications/luci-app-keepalived/root/usr/share/luci/menu.d/luci-app-keepalived.json109
-rw-r--r--applications/luci-app-keepalived/root/usr/share/rpcd/acl.d/luci-app-keepalived.json17
15 files changed, 1376 insertions, 0 deletions
diff --git a/applications/luci-app-keepalived/Makefile b/applications/luci-app-keepalived/Makefile
new file mode 100644
index 0000000000..81b0cc2635
--- /dev/null
+++ b/applications/luci-app-keepalived/Makefile
@@ -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
+
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/globals.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/globals.js
new file mode 100644
index 0000000000..5329d3304c
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/globals.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/ipaddress.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/ipaddress.js
new file mode 100644
index 0000000000..0cdce65bef
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/ipaddress.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/overview.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/overview.js
new file mode 100644
index 0000000000..7e261bf82d
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/overview.js
@@ -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
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/peers.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/peers.js
new file mode 100644
index 0000000000..059fc1dd6c
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/peers.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/route.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/route.js
new file mode 100644
index 0000000000..cf2454c7d4
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/route.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/script.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/script.js
new file mode 100644
index 0000000000..99d5af29f2
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/script.js
@@ -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();
+ }
+
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/servers.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/servers.js
new file mode 100644
index 0000000000..1756f4b9fa
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/servers.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/track_interface.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/track_interface.js
new file mode 100644
index 0000000000..b407d0eef8
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/track_interface.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/url.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/url.js
new file mode 100644
index 0000000000..5e311fd255
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/url.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_instance.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_instance.js
new file mode 100644
index 0000000000..f9293d6c20
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_instance.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_sync_group.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_sync_group.js
new file mode 100644
index 0000000000..69ed8f2435
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/keepalived/vrrp_sync_group.js
@@ -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();
+ }
+});
diff --git a/applications/luci-app-keepalived/htdocs/luci-static/resources/view/status/include/35_keepalived.js b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/status/include/35_keepalived.js
new file mode 100644
index 0000000000..4f47d14980
--- /dev/null
+++ b/applications/luci-app-keepalived/htdocs/luci-static/resources/view/status/include/35_keepalived.js
@@ -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
+ ]);
+ },
+});
diff --git a/applications/luci-app-keepalived/root/usr/share/luci/menu.d/luci-app-keepalived.json b/applications/luci-app-keepalived/root/usr/share/luci/menu.d/luci-app-keepalived.json
new file mode 100644
index 0000000000..d839ab935c
--- /dev/null
+++ b/applications/luci-app-keepalived/root/usr/share/luci/menu.d/luci-app-keepalived.json
@@ -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"
+ }
+ }
+}
diff --git a/applications/luci-app-keepalived/root/usr/share/rpcd/acl.d/luci-app-keepalived.json b/applications/luci-app-keepalived/root/usr/share/rpcd/acl.d/luci-app-keepalived.json
new file mode 100644
index 0000000000..0c8b676e61
--- /dev/null
+++ b/applications/luci-app-keepalived/root/usr/share/rpcd/acl.d/luci-app-keepalived.json
@@ -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" ]
+ }
+ }
+ }
+}