diff options
Diffstat (limited to 'applications/luci-app-pbr/htdocs/luci-static')
-rw-r--r-- | applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js | 313 | ||||
-rw-r--r-- | applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js | 246 |
2 files changed, 559 insertions, 0 deletions
diff --git a/applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js b/applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js new file mode 100644 index 0000000000..8cd36bca7c --- /dev/null +++ b/applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js @@ -0,0 +1,313 @@ +// Copyright 2022 Stan Grishin <stangri@melmac.ca> +// This code wouldn't have been possible without help from [@vsviridov](https://github.com/vsviridov) + +"require ui"; +"require rpc"; +"require uci"; +"require form"; +"require baseclass"; + +var pkg = { + get Name() { + return "pbr"; + }, + get URL() { + return "https://docs.openwrt.melmac.net/" + pkg.Name + "/"; + }, +}; + +var getGateways = rpc.declare({ + object: "luci." + pkg.Name, + method: "getGateways", + params: ["name"], +}); + +var getInitList = rpc.declare({ + object: "luci." + pkg.Name, + method: "getInitList", + params: ["name"], +}); + +var getInitStatus = rpc.declare({ + object: "luci." + pkg.Name, + method: "getInitStatus", + params: ["name"], +}); + +var getInterfaces = rpc.declare({ + object: "luci." + pkg.Name, + method: "getInterfaces", + params: ["name"], +}); + +var getPlatformSupport = rpc.declare({ + object: "luci." + pkg.Name, + method: "getPlatformSupport", + params: ["name"], +}); + +var _setInitAction = rpc.declare({ + object: "luci." + pkg.Name, + method: "setInitAction", + params: ["name", "action"], + expect: { result: false }, +}); + +var RPC = { + listeners: [], + on: function on(event, callback) { + var pair = { event: event, callback: callback } + this.listeners.push(pair); + return function unsubscribe() { + this.listeners = this.listeners.filter(function (listener) { + return listener !== pair; + }); + }.bind(this); + }, + emit: function emit(event, data) { + this.listeners.forEach(function (listener) { + if (listener.event === event) { + listener.callback(data); + } + }); + }, + getInitList: function getInitList(name) { + getInitList(name).then(function (result) { + this.emit('getInitList', result); + }.bind(this)); + }, + getInitStatus: function getInitStatus(name) { + getInitStatus(name).then(function (result) { + this.emit('getInitStatus', result); + }.bind(this)); + }, + getGateways: function getGateways(name) { + getGateways(name).then(function (result) { + this.emit('getGateways', result); + }.bind(this)); + }, + getPlatformSupport: function getPlatformSupport(name) { + getPlatformSupport(name).then(function (result) { + this.emit('getPlatformSupport', result); + }.bind(this)); + }, + getInterfaces: function getInterfaces(name) { + getInterfaces(name).then(function (result) { + this.emit('getInterfaces', result); + }.bind(this)); + }, + setInitAction: function setInitAction(name, action) { + _setInitAction(name, action).then(function (result) { + this.emit('setInitAction', result); + }.bind(this)); + }, +} + +var status = baseclass.extend({ + render: function () { + return Promise.all([ + L.resolveDefault(getInitStatus(), {}), +// L.resolveDefault(getGateways(), {}), + ]).then(function (data) { +// var replyStatus = data[0]; +// var replyGateways = data[1]; + var reply = data[0][pkg.Name]; + var text; + var header = E('h2', {}, _("Policy Based Routing - Status")); + var statusTitle = E('label', { class: 'cbi-value-title' }, _("Service Status")); + if (reply.version) { + if (reply.running) { + if (reply.running_iptables) { + text = _("Running (version: %s using iptables)").format(reply.version); + } + else if (reply.running_nft) { + text = _("Running (version: %s using nft)").format(reply.version); + } + else { + text = _("Running (version: %s)").format(reply.version); + } + } + else { + if (reply.enabled) { + text = _("Stopped (version: %s)").format(reply.version); + } + else { + text = _("Stopped (Disabled)"); + } + } + } + else { + text = _("Not installed or not found"); + } + var statusText = E('div', {}, text); + var statusField = E('div', { class: 'cbi-value-field' }, statusText); + var statusDiv = E('div', { class: 'cbi-value' }, [statusTitle, statusField]); + + var gatewaysDiv = []; + if (reply.gateways) { + var gatewaysTitle = E('label', { class: 'cbi-value-title' }, _("Service Gateways")); + text = _("The %s indicates default gateway. See the %sREADME%s for details.").format("<strong>✓</strong>", + "<a href=\"" + pkg.URL + "#a-word-about-default-routing \" target=\"_blank\">", "</a>") + var gatewaysDescr = E('div', { class: 'cbi-value-description' }, text); + var gatewaysText = E('div', {}, reply.gateways); + var gatewaysField = E('div', { class: 'cbi-value-field' }, [gatewaysText, gatewaysDescr]); + gatewaysDiv = E('div', { class: 'cbi-value' }, [gatewaysTitle, gatewaysField]); + } + + var warningsDiv = []; + if (reply.warnings && reply.warnings.length) { + var textLabelsTable = { + warningResolverNotSupported: _("Resolver set (%s) is not supported on this system.").format(uci.get(pkg.Name, 'config', 'resolver_set')), + warningAGHVersionTooLow: _("Installed AdGuardHome (%s) doesn't support 'ipset_file' option."), + warningPolicyProcess: _("%s") + }; + var warningsTitle = E('label', { class: 'cbi-value-title' }, _("Service Warnings")); + var text = ""; + (reply.warnings).forEach(element => { + text += (textLabelsTable[element.id]).format(element.extra || ' ') + "<br />"; + }); + var warningsText = E('div', {}, text); + var warningsField = E('div', { class: 'cbi-value-field' }, warningsText); + warningsDiv = E('div', { class: 'cbi-value' }, [warningsTitle, warningsField]); + } + + var errorsDiv = []; + if (reply.errors && reply.errors.length) { + var textLabelsTable = { + errorConfigValidation: _("Config (%s) validation failure!").format('/etc/config/' + pkg.Name), + errorNoIpFull: _("ip-full binary cannot be found!"), + errorNoIpset: _("Resolver set support (%s) requires ipset, but ipset binary cannot be found!").format(uci.get(pkg.Name, 'config', 'resolver_set')), + errorNoNft: _("Resolver set support (%s) requires nftables, but nft binary cannot be found!").format(uci.get(pkg.Name, 'config', 'resolver_set')), + errorResolverNotSupported: _("Resolver set (%s) is not supported on this system!").format(uci.get(pkg.Name, 'config', 'resolver_set')), + errorServiceDisabled: _("The %s service is currently disabled!").format(pkg.Name), + errorNoWanGateway: _("The %s service failed to discover WAN gateway!").format(pkg.Name), + errorIpsetNameTooLong: _("The ipset name '%s' is longer than allowed 31 characters!"), + errorNftsetNameTooLong: _("The nft set name '%s' is longer than allowed 31 characters!"), + errorUnexpectedExit: _("Unexpected exit or service termination: '%s'!"), + errorPolicyNoSrcDest: _("Policy '%s' has no source/destination parameters!"), + errorPolicyNoInterface: _("Policy '%s' has no assigned interface!"), + errorPolicyUnknownInterface: _("Policy '%s' has an unknown interface!"), + errorPolicyProcess: _("%s"), + errorFailedSetup: _("Failed to set up '%s'!"), + errorFailedReload: _("Failed to reload '%s'!"), + errorUserFileNotFound: _("Custom user file '%s' not found or empty!"), + ererrorUserFileSyntax: _("Syntax error in custom user file '%s'!"), + errorUserFileRunning: _("Error running custom user file '%s'!"), + errorUserFileNoCurl: _("Use of 'curl' is detected in custom user file '%s', but 'curl' isn't installed!"), + errorNoGateways: _("Failed to set up any gateway!") + }; + var errorsTitle = E('label', { class: 'cbi-value-title' }, _("Service Errors")); + var text = ""; + (reply.errors).forEach(element => { + text += (textLabelsTable[element.id]).format(element.extra || ' ') + "<br />"; + }); + var errorsText = E('div', {}, text); + var errorsField = E('div', { class: 'cbi-value-field' }, errorsText); + errorsDiv = E('div', { class: 'cbi-value' }, [errorsTitle, errorsField]); + } + + var btn_gap = E('span', {}, '  '); + var btn_gap_long = E('span', {}, '        '); + + var btn_start = E('button', { + 'class': 'btn cbi-button cbi-button-apply', + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E('p', { 'class': 'spinning' }, _('Starting %s service').format(pkg.Name)) + ]); + return RPC.setInitAction(pkg.Name, 'start'); + } + }, _('Start')); + + var btn_action = E('button', { + 'class': 'btn cbi-button cbi-button-apply', + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E('p', { 'class': 'spinning' }, _('Restarting %s service').format(pkg.Name)) + ]); + return RPC.setInitAction(pkg.Name, 'restart'); + } + }, _('Restart')); + + var btn_stop = E('button', { + 'class': 'btn cbi-button cbi-button-reset', + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E('p', { 'class': 'spinning' }, _('Stopping %s service').format(pkg.Name)) + ]); + return RPC.setInitAction(pkg.Name, 'stop'); + } + }, _('Stop')); + + var btn_enable = E('button', { + 'class': 'btn cbi-button cbi-button-apply', + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E('p', { 'class': 'spinning' }, _('Enabling %s service').format(pkg.Name)) + ]); + return RPC.setInitAction(pkg.Name, 'enable'); + } + }, _('Enable')); + + var btn_disable = E('button', { + 'class': 'btn cbi-button cbi-button-reset', + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E('p', { 'class': 'spinning' }, _('Disabling %s service').format(pkg.Name)) + ]); + return RPC.setInitAction(pkg.Name, 'disable'); + } + }, _('Disable')); + + if (reply.enabled) { + btn_enable.disabled = true; + btn_disable.disabled = false; + if (reply.running) { + btn_start.disabled = true; + btn_action.disabled = false; + btn_stop.disabled = false; + } + else { + btn_start.disabled = false; + btn_action.disabled = true; + btn_stop.disabled = true; + } + } + else { + btn_start.disabled = true; + btn_action.disabled = true; + btn_stop.disabled = true; + btn_enable.disabled = false; + btn_disable.disabled = true; + } + + var buttonsTitle = E('label', { class: 'cbi-value-title' }, _("Service Control")) + var buttonsText = E('div', {}, [btn_start, btn_gap, btn_action, btn_gap, btn_stop, btn_gap_long, btn_enable, btn_gap, btn_disable]); + var buttonsField = E('div', { class: 'cbi-value-field' }, buttonsText); + if (reply.version) { + var buttonsDiv = E('div', { class: 'cbi-value' }, [buttonsTitle, buttonsField]); + } + else { + var buttonsDiv = []; + } + + return E('div', {}, [header, statusDiv, gatewaysDiv, warningsDiv, errorsDiv, buttonsDiv]); + }); + }, +}); + +RPC.on('setInitAction', function (reply) { + ui.hideModal(); + location.reload(); +}); + +return L.Class.extend({ + status: status, + getInterfaces: getInterfaces, + getPlatformSupport: getPlatformSupport +}); diff --git a/applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js b/applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js new file mode 100644 index 0000000000..32bc6cb2de --- /dev/null +++ b/applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js @@ -0,0 +1,246 @@ +// Copyright 2022 Stan Grishin <stangri@melmac.ca> +// This code wouldn't have been possible without help from [@vsviridov](https://github.com/vsviridov) + +'use strict'; +'require form'; +'require rpc'; +'require uci'; +'require view'; +'require pbr.status as pbr'; + +var pkg = { + get Name() { return 'pbr'; }, + get URL() { return 'https://docs.openwrt.melmac.net/' + pkg.Name + '/'; } +}; + +return view.extend({ + load: function () { + return Promise.all([ + uci.load(pkg.Name) + ]); + }, + + render: function () { + return Promise.all([ + L.resolveDefault(pbr.getInterfaces(), {}), + L.resolveDefault(pbr.getPlatformSupport(), {}), + ]).then(function (data) { + var arrInterfaces = data[0][pkg.Name].interfaces; + var replyPlatform = data[1][pkg.Name]; + var status, m, s, o; + + status = new pbr.status(); + m = new form.Map(pkg.Name, _("Policy Based Routing - Configuration")); + + s = m.section(form.NamedSection, 'config', pkg.Name); + s.tab("tab_basic", _("Basic Configuration")); + s.tab("tab_advanced", _("Advanced Configuration"), + _("%sWARNING:%s Please make sure to check the %sREADME%s before changing anything in this section! " + + "Change any of the settings below with extreme caution!%s").format( + "<br/>    <b>", "</b>", + "<a href=\"" + pkg.URL + "#service-configuration-settings \" target=\"_blank\">", "</a>", "<br/><br/>")); + s.tab("tab_webui", _("Web UI Configuration")) + + o = s.taboption("tab_basic", form.ListValue, "verbosity", _("Output verbosity"), + _("Controls both system log and console output verbosity.")); + o.value("0", _("Suppress/No output")); + o.value("1", _("Condensed output")); + o.value("2", _("Verbose output")); + o.default = "2"; + + o = s.taboption("tab_basic", form.ListValue, "strict_enforcement", _("Strict enforcement"), + _("See the %sREADME%s for details.").format( + "<a href=\"" + pkg.URL + "#strict-enforcement\" target=\"_blank\">", "</a>")); + o.value("0", _("Do not enforce policies when their gateway is down")); + o.value("1", _("Strictly enforce policies when their gateway is down")); + o.default = "1"; + + var text = ""; + if (!(replyPlatform.adguardhome_ipset_support)) { + text += _("The %s is not supported on this system.").format("<i>adguardhome.ipset</i>") + "<br />" + } + if (!(replyPlatform.dnsmasq_ipset_support)) { + text += _("The %s is not supported on this system.").format("<i>dnsmasq.ipset</i>") + "<br />" + } + if (!(replyPlatform.dnsmasq_nftset_support)) { + text += _("The %s is not supported on this system.").format("<i>dnsmasq.nftset</i>") + "<br />" + } + text += _("Please check the %sREADME%s before changing this option.").format( + "<a href=\"" + pkg.URL + "#use-resolvers-set-support\" target=\"_blank\">", "</a>"); + o = s.taboption("tab_basic", form.ListValue, "resolver_set", _("Use resolver set support for domains"), text); + o.value("none", _("Disabled")); + if (replyPlatform.adguardhome_ipset_support) { + o.value("adguardhome.ipset", _("AdGuardHome ipset")); + o.default = ("adguardhome.ipset", _("AdGuardHome ipset")); + } + if (replyPlatform.dnsmasq_ipset_support) { + o.value("dnsmasq.ipset", _("Dnsmasq ipset")); + o.default = ("dnsmasq.ipset", _("Dnsmasq ipset")); + } + if (replyPlatform.dnsmasq_nftset_support) { + o.value("dnsmasq.nftset", _("Dnsmasq nft set")); + o.default = ("dnsmasq.nftset", _("Dnsmasq nft set")); + } + + o = s.taboption("tab_basic", form.ListValue, "ipv6_enabled", _("IPv6 Support")); + o.value("0", _("Disabled")); + o.value("1", _("Enabled")); + + o = s.taboption("tab_advanced", form.DynamicList, "supported_interface", _("Supported Interfaces"), + _("Allows to specify the list of interface names (in lower case) to be explicitly supported by the service. " + + "Can be useful if your OpenVPN tunnels have dev option other than tun* or tap*.")); + o.optional = false; + + o = s.taboption("tab_advanced", form.DynamicList, "ignored_interface", _("Ignored Interfaces"), + _("Allows to specify the list of interface names (in lower case) to be ignored by the service. " + + "Can be useful if running both VPN server and VPN client on the router.")); + o.optional = false; + + o = s.taboption("tab_advanced", form.ListValue, "rule_create_option", _("Rule Create option"), + _("Select Add for -A/add and Insert for -I/Insert.")); + o.value("add", _("Add")); + o.value("insert", _("Insert")); + o.default = "add"; + + o = s.taboption("tab_advanced", form.ListValue, "icmp_interface", _("Default ICMP Interface"), + _("Force the ICMP protocol interface.")); + o.value("", _("No Change")); + arrInterfaces.forEach(element => { + if (element.toLowerCase() !== "ignore") { + o.value(element); + } + }); + o.rmempty = true; + + o = s.taboption("tab_advanced", form.Value, "wan_tid", _("WAN Table ID"), + _("Starting (WAN) Table ID number for tables created by the service.")); + o.rmempty = true; + o.placeholder = "201"; + o.datatype = "and(uinteger, min(201))"; + + o = s.taboption("tab_advanced", form.Value, "wan_mark", _("WAN Table FW Mark"), + _("Starting (WAN) FW Mark for marks used by the service. High starting mark is " + + "used to avoid conflict with SQM/QoS. Change with caution together with") + + " " + _("Service FW Mask") + "."); + o.rmempty = true; + o.placeholder = "010000"; + o.datatype = "hexstring"; + + o = s.taboption("tab_advanced", form.Value, "fw_mask", _("Service FW Mask"), + _("FW Mask used by the service. High mask is used to avoid conflict with SQM/QoS. " + + "Change with caution together with") + " " + _("WAN Table FW Mark") + "."); + o.rmempty = true; + o.placeholder = "ff0000"; + o.datatype = "hexstring"; + + o = s.taboption("tab_webui", form.ListValue, "webui_show_ignore_target", _("Add Ignore Target"), + _("Adds 'ignore' to the list of interfaces for policies. See the %sREADME%s for details.").format( + "<a href=\"" + pkg.URL + "#ignore-target\" target=\"_blank\">", "</a>")); + o.value("0", _("Disabled")) + o.value("1", _("Enabled")) + o.default = "0"; + o.optional = false; + + o = s.taboption("tab_webui", form.DynamicList, "webui_supported_protocol", _("Supported Protocols"), + _("Display these protocols in protocol column in Web UI.")); + o.optional = false; + + s = m.section(form.GridSection, 'policy', _('Policies'), + _("Name, interface and at least one other field are required. Multiple local and remote " + + "addresses/devices/domains and ports can be space separated. Placeholders below represent just " + + "the format/syntax and will not be used if fields are left blank.")); + s.rowcolors = true; + s.sortable = true; + s.anonymous = true; + s.addremove = true; + + o = s.option(form.Flag, "enabled", _("Enabled")); + o.default = "1"; + o.editable = true; + + o = s.option(form.Value, "name", _("Name")); + + o = s.option(form.Value, "src_addr", _("Local addresses / devices")); + o.datatype = "list(neg(or(cidr,host,ipmask,ipaddr,macaddr,network)))"; + o.rmempty = true; + o.default = ""; + + o = s.option(form.Value, "src_port", _("Local ports")); + o.datatype = "list(neg(or(portrange,port)))"; + o.placeholder = "0-65535"; + o.rmempty = true; + o.default = ""; + + o = s.option(form.Value, "dest_addr", _("Remote addresses / domains")); + o.datatype = "list(neg(or(cidr,host,ipmask,ipaddr,macaddr,network)))"; + o.rmempty = true; + o.default = ""; + + o = s.option(form.Value, "dest_port", _("Remote ports")); + o.datatype = "list(neg(or(portrange,port)))"; + o.placeholder = "0-65535"; + o.rmempty = true; + o.default = ""; + + o = s.option(form.ListValue, "proto", _("Protocol")); + var proto = L.toArray(uci.get(pkg.Name, "config", "webui_supported_protocol")); + if (!proto.length) { + proto = ["all", "tcp", "udp", "tcp udp", "icmp"] + } + proto.forEach(element => { + if (element === "all") { + o.value("", _("all")); + o.default = ("", _("all")); + } + else { + o.value(element.toLowerCase()); + } + }); + o.rmempty = true; + + o = s.option(form.ListValue, "chain", _("Chain")); + o.value("", "prerouting"); + o.value("forward", "forward"); + o.value("input", "input"); + o.value("output", "output"); + o.value("postrouting", "postrouting"); + o.default = ("", "prerouting"); + o.rmempty = true; + + o = s.option(form.ListValue, "interface", _("Interface")); + arrInterfaces.forEach(element => { + o.value(element); + }); + o.datatype = "network"; + o.rmempty = false; + + s = m.section(form.NamedSection, 'config', pkg.Name, _("DSCP Tagging"), + _("Set DSCP tags (in range between 1 and 63) for specific interfaces. See the %sREADME%s for details.").format( + "<a href=\"" + pkg.URL + "#dscp-tag-based-policies" + "\" target=\"_blank\">", "</a>")); + arrInterfaces.forEach(element => { + if (element.toLowerCase() !== "ignore") { + o = s.option(form.Value, element + "_dscp", element.toUpperCase() + " " + _("DSCP Tag")); + o.datatype = "and(uinteger, min(1), max(63))"; + } + }); + + s = m.section(form.GridSection, 'include', _("Custom User File Includes"), + _("Run the following user files after setting up but before restarting DNSMASQ. " + + "See the %sREADME%s for details.").format( + "<a href=\"" + pkg.URL + "#custom-user-files\" target=\"_blank\">", "</a>")); + s.sortable = true; + s.anonymous = true; + s.addremove = true; + + o = s.option(form.Flag, "enabled", _("Enabled")); + o.optional = false; + o.editable = true; + + o = s.option(form.Value, "path", _("Path")); + o.optional = false; + o.editable = true; + + return Promise.all([status.render(), m.render()]); + }) + } +}); |