diff options
Diffstat (limited to 'applications/luci-app-mwan3/luasrc')
17 files changed, 1485 insertions, 0 deletions
diff --git a/applications/luci-app-mwan3/luasrc/controller/mwan3.lua b/applications/luci-app-mwan3/luasrc/controller/mwan3.lua new file mode 100644 index 0000000000..2d46953e55 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/controller/mwan3.lua @@ -0,0 +1,314 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +module("luci.controller.mwan3", package.seeall) + +sys = require "luci.sys" +ut = require "luci.util" + +ip = "ip -4 " + +function index() + if not nixio.fs.access("/etc/config/mwan3") then + return + end + + entry({"admin", "status", "mwan"}, + alias("admin", "status", "mwan", "overview"), + _("Load Balancing"), 600) + + entry({"admin", "status", "mwan", "overview"}, + template("mwan/status_interface")) + entry({"admin", "status", "mwan", "detail"}, + template("mwan/status_detail")) + entry({"admin", "status", "mwan", "diagnostics"}, + template("mwan/status_diagnostics")) + entry({"admin", "status", "mwan", "troubleshooting"}, + template("mwan/status_troubleshooting")) + entry({"admin", "status", "mwan", "interface_status"}, + call("mwan_Status")) + entry({"admin", "status", "mwan", "detailed_status"}, + call("detailedStatus")) + entry({"admin", "status", "mwan", "diagnostics_display"}, + call("diagnosticsData"), nil).leaf = true + entry({"admin", "status", "mwan", "troubleshooting_display"}, + call("troubleshootingData")) + + + entry({"admin", "network", "mwan"}, + alias("admin", "network", "mwan", "interface"), + _("Load Balancing"), 600) + + entry({"admin", "network", "mwan", "globals"}, + cbi("mwan/globalsconfig"), + _("Globals"), 5).leaf = true + entry({"admin", "network", "mwan", "interface"}, + arcombine(cbi("mwan/interface"), cbi("mwan/interfaceconfig")), + _("Interfaces"), 10).leaf = true + entry({"admin", "network", "mwan", "member"}, + arcombine(cbi("mwan/member"), cbi("mwan/memberconfig")), + _("Members"), 20).leaf = true + entry({"admin", "network", "mwan", "policy"}, + arcombine(cbi("mwan/policy"), cbi("mwan/policyconfig")), + _("Policies"), 30).leaf = true + entry({"admin", "network", "mwan", "rule"}, + arcombine(cbi("mwan/rule"), cbi("mwan/ruleconfig")), + _("Rules"), 40).leaf = true + entry({"admin", "network", "mwan", "notify"}, + form("mwan/notify"), + _("Notification"), 50).leaf = true +end + +function mwan_Status() + local status = ut.ubus("mwan3", "status", {}) + + luci.http.prepare_content("application/json") + if status ~= nil then + luci.http.write_json(status) + else + luci.http.write_json({}) + end +end + +function detailedStatus() + local statusInfo = ut.trim(sys.exec("/usr/sbin/mwan3 status")) + luci.http.prepare_content("text/plain") + if statusInfo ~= "" then + luci.http.write(statusInfo) + else + luci.http.write("Unable to get status information") + end +end + +function diagnosticsData(interface, task) + function getInterfaceNumber(interface) + local number = 0 + local interfaceNumber + local uci = require "luci.model.uci".cursor() + uci:foreach("mwan3", "interface", + function (section) + number = number+1 + if section[".name"] == interface then + interfaceNumber = number + end + end + ) + return interfaceNumber + end + + function diag_command(cmd, device, addr) + if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then + local util = io.popen(cmd %{ut.shellquote(device), ut.shellquote(addr)}) + if util then + while true do + local ln = util:read("*l") + if not ln then break end + luci.http.write(ln) + luci.http.write("\n") + end + util:close() + end + return + end + end + + function get_gateway(inteface) + local gateway = nil + local dump = nil + + dump = require("luci.util").ubus("network.interface.%s_4" % interface, "status", {}) + if not dump then + dump = require("luci.util").ubus("network.interface.%s" % interface, "status", {}) + end + + if dump and dump.route then + local _, route + for _, route in ipairs(dump.route) do + if dump.route[_].target == "0.0.0.0" then + gateway = dump.route[_].nexthop + end + end + end + return gateway + end + + local mArray = {} + local results = "" + local number = getInterfaceNumber(interface) + + local uci = require "luci.model.uci".cursor(nil, "/var/state") + local nw = require "luci.model.network".init() + local network = nw:get_network(interface) + local device = network and network:ifname() + + luci.http.prepare_content("text/plain") + if device then + if task == "ping_gateway" then + local gateway = get_gateway(interface) + if gateway ~= nil then + diag_command("ping -I %s -c 5 -W 1 %s 2>&1", device, gateway) + else + luci.http.prepare_content("text/plain") + luci.http.write(string.format("No gateway for interface %s found.", interface)) + end + elseif task == "ping_trackips" then + local trackips = uci:get("mwan3", interface, "track_ip") + if #trackips > 0 then + for i in pairs(trackips) do + diag_command("ping -I %s -c 5 -W 1 %s 2>&1", device, trackips[i]) + end + else + luci.http.write(string.format("No tracking Hosts for interface %s defined.", interface)) + end + elseif task == "check_rules" then + local number = getInterfaceNumber(interface) + local iif = 1000 + number + local fwmark = 2000 + number + local iif_rule = sys.exec(string.format("ip rule | grep %d", iif)) + local fwmark_rule = sys.exec(string.format("ip rule | grep %d", fwmark)) + if iif_rule ~= "" and fwmark_rule ~= "" then + luci.http.write(string.format("All required IP rules for interface %s found", interface)) + luci.http.write("\n") + luci.http.write(fwmark_rule) + luci.http.write(iif_rule) + elseif iif_rule == "" and fwmark_rule ~= "" then + luci.http.write(string.format("Only one IP rules for interface %s found", interface)) + luci.http.write("\n") + luci.http.write(fwmark_rule) + elseif iif_rule ~= "" and fwmark_rule == "" then + luci.http.write(string.format("Only one IP rules for interface %s found", interface)) + luci.http.write("\n") + luci.http.write(iif_rule) + else + luci.http.write(string.format("Missing both IP rules for interface %s", interface)) + end + elseif task == "check_routes" then + local number = getInterfaceNumber(interface) + local routeTable = sys.exec(string.format("ip route list table %s", number)) + if routeTable ~= "" then + luci.http.write(string.format("Routing table %s for interface %s found", number, interface)) + luci.http.write("\n") + luci.http.write(routeTable) + else + luci.http.write(string.format("Routing table %s for interface %s not found", number, interface)) + end + elseif task == "hotplug_ifup" then + os.execute(string.format("/usr/sbin/mwan3 ifup %s", ut.shellquote(interface))) + luci.http.write(string.format("Hotplug ifup sent to interface %s", interface)) + elseif task == "hotplug_ifdown" then + os.execute(string.format("/usr/sbin/mwan3 ifdown %s", ut.shellquote(interface))) + luci.http.write(string.format("Hotplug ifdown sent to interface %s", interface)) + else + luci.http.write("Unknown task") + end + else + luci.http.write(string.format("Unable to perform diagnostic tests on %s.", interface)) + luci.http.write("\n") + luci.http.write("There is no physical or virtual device associated with this interface.") + end +end + +function troubleshootingData() + local ver = require "luci.version" + local dash = "-------------------------------------------------" + + luci.http.prepare_content("text/plain") + + luci.http.write("\n") + luci.http.write("\n") + luci.http.write("Software-Version") + luci.http.write("\n") + luci.http.write(dash) + luci.http.write("\n") + if ver.distversion then + luci.http.write(string.format("OpenWrt - %s", ver.distversion)) + luci.http.write("\n") + else + luci.http.write("OpenWrt - unknown") + luci.http.write("\n") + end + + if ver.luciversion then + luci.http.write(string.format("LuCI - %s", ver.luciversion)) + luci.http.write("\n") + else + luci.http.write("LuCI - unknown") + luci.http.write("\n") + end + + luci.http.write("\n") + luci.http.write("\n") + local output = ut.trim(sys.exec("ip a show")) + luci.http.write("Output of \"ip a show\"") + luci.http.write("\n") + luci.http.write(dash) + luci.http.write("\n") + if output ~= "" then + luci.http.write(output) + luci.http.write("\n") + else + luci.http.write("No data found") + luci.http.write("\n") + end + + luci.http.write("\n") + luci.http.write("\n") + local output = ut.trim(sys.exec("ip route show")) + luci.http.write("Output of \"ip route show\"") + luci.http.write("\n") + luci.http.write(dash) + luci.http.write("\n") + if output ~= "" then + luci.http.write(output) + luci.http.write("\n") + else + luci.http.write("No data found") + luci.http.write("\n") + end + + luci.http.write("\n") + luci.http.write("\n") + local output = ut.trim(sys.exec("ip rule show")) + luci.http.write("Output of \"ip rule show\"") + luci.http.write("\n") + luci.http.write(dash) + luci.http.write("\n") + if output ~= "" then + luci.http.write(output) + luci.http.write("\n") + else + luci.http.write("No data found") + luci.http.write("\n") + end + + luci.http.write("\n") + luci.http.write("\n") + luci.http.write("Output of \"ip route list table 1-250\"") + luci.http.write("\n") + luci.http.write(dash) + luci.http.write("\n") + for i=1,250 do + local output = ut.trim(sys.exec(string.format("ip route list table %d", i))) + if output ~= "" then + luci.http.write(string.format("Table %s: ", i)) + luci.http.write(output) + luci.http.write("\n") + end + end + + luci.http.write("\n") + luci.http.write("\n") + local output = ut.trim(sys.exec("iptables -L -t mangle -v -n")) + luci.http.write("Output of \"iptables -L -t mangle -v -n\"") + luci.http.write("\n") + luci.http.write(dash) + luci.http.write("\n") + if output ~= "" then + luci.http.write(output) + luci.http.write("\n") + else + luci.http.write("No data found") + luci.http.write("\n") + end +end diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/globalsconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/globalsconfig.lua new file mode 100644 index 0000000000..d8f90e1e25 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/globalsconfig.lua @@ -0,0 +1,31 @@ +-- Copyright 2017 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +local net = require "luci.model.network".init() + + +m = Map("mwan3", translate("MWAN - Globals")) + +s = m:section(NamedSection, "globals", "globals", nil) +n = s:option(ListValue, "local_source", + translate("Local source interface"), + translate("Use the IP address of this interface as source IP " .. + "address for traffic initiated by the router itself")) +n:value("none") +n.default = "none" +for _, net in ipairs(net:get_networks()) do + if net:name() ~= "loopback" then + n:value(net:name()) + end +end +n.rmempty = false + +mask = s:option( + Value, + "mmx_mask", + translate("Firewall mask"), + translate("Enter value in hex, starting with <code>0x</code>")) +mask.datatype = "hex(4)" +mask.default = "0xff00" + +return m diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua new file mode 100644 index 0000000000..162b388d17 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua @@ -0,0 +1,240 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" +uci = require "uci" + + +function interfaceWarnings(overview, count, iface_max) + local warnings = "" + if count <= iface_max then + warnings = string.format("<strong>%s</strong><br />", + translatef("There are currently %d of %d supported interfaces configured", count, iface_max) + ) + else + warnings = string.format("<strong>%s</strong><br />", + translatef("WARNING: %d interfaces are configured exceeding the maximum of %d!", count, iface_max) + ) + end + + for i, k in pairs(overview) do + if overview[i]["network"] == false then + warnings = warnings .. string.format("<strong>%s</strong><br />", + translatef("WARNING: Interface %s are not found in /etc/config/network", i) + ) + end + + if overview[i]["default_route"] == false then + warnings = warnings .. string.format("<strong>%s</strong><br />", + translatef("WARNING: Interface %s has no default route in the main routing table", i) + ) + end + + if overview[i]["reliability"] == false then + warnings = warnings .. string.format("<strong>%s</strong><br />", + translatef("WARNING: Interface %s has a higher reliability " .. + "requirement than tracking hosts (%d)", i, overview[i]["tracking"]) + ) + end + + if overview[i]["duplicate_metric"] == true then + warnings = warnings .. string.format("<strong>%s</strong><br />", + translatef("WARNING: Interface %s has a duplicate metric %s configured", i, overview[i]["metric"]) + ) + end + end + + return warnings +end + +function configCheck() + local overview = {} + local count = 0 + local duplicate_metric = {} + uci.cursor():foreach("mwan3", "interface", + function (section) + local uci = uci.cursor(nil, "/var/state") + local iface = section[".name"] + overview[iface] = {} + count = count + 1 + local network = uci:get("network", iface) + overview[iface]["network"] = false + if network ~= nil then + overview[iface]["network"] = true + + local device = uci:get("network", iface, "ifname") + if device ~= nil then + overview[iface]["device"] = device + end + + local metric = uci:get("network", iface, "metric") + if metric ~= nil then + overview[iface]["metric"] = metric + overview[iface]["duplicate_metric"] = false + for _, m in ipairs(duplicate_metric) do + if m == metric then + overview[iface]["duplicate_metric"] = true + end + end + table.insert(duplicate_metric, metric) + end + + local dump = require("luci.util").ubus("network.interface.%s" % iface, "status", {}) + overview[iface]["default_route"] = false + if dump and dump.route then + local _, route + for _, route in ipairs(dump.route) do + if dump.route[_].target == "0.0.0.0" then + overview[iface]["default_route"] = true + end + end + end + end + + local trackingNumber = uci:get("mwan3", iface, "track_ip") + overview[iface]["tracking"] = 0 + if trackingNumber and #trackingNumber > 0 then + overview[iface]["tracking"] = #trackingNumber + overview[iface]["reliability"] = false + local reliabilityNumber = tonumber(uci:get("mwan3", iface, "reliability")) + if reliabilityNumber and reliabilityNumber <= #trackingNumber then + overview[iface]["reliability"] = true + end + end + end + ) + + -- calculate iface_max usage from firewall mmx_mask + function bit(p) + return 2 ^ (p - 1) + end + function hasbit(x, p) + return x % (p + p) >= p + end + function setbit(x, p) + return hasbit(x, p) and x or x + p + end + + local uci = require("uci").cursor(nil, "/var/state") + local mmx_mask = uci:get("mwan3", "globals", "mmx_mask") or "0x3F00" + local number = tonumber(mmx_mask, 16) + local bits = 0 + local iface_max = 0 + for i=1,16 do + if hasbit(number, bit(i)) then + bits = bits + 1 + iface_max = setbit( iface_max, bit(bits)) + end + end + + -- subtract blackhole, unreachable and default table from iface_max + iface_max = iface_max - 3 + + return overview, count, iface_max +end + +m5 = Map("mwan3", translate("MWAN - Interfaces"), + interfaceWarnings(configCheck())) + +mwan_interface = m5:section(TypedSection, "interface", nil, + translate("MWAN supports up to 252 physical and/or logical interfaces<br />" .. + "MWAN requires that all interfaces have a unique metric configured in /etc/config/network<br />" .. + "Names must match the interface name found in /etc/config/network<br />" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" .. + "Interfaces may not share the same name as configured members, policies or rules")) +mwan_interface.addremove = true +mwan_interface.dynamic = false +mwan_interface.sectionhead = translate("Interface") +mwan_interface.sortable = false +mwan_interface.template = "cbi/tblsection" +mwan_interface.extedit = dsp.build_url("admin", "network", "mwan", "interface", "%s") +function mwan_interface.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(dsp.build_url("admin", "network", "mwan", "interface", section)) +end + +enabled = mwan_interface:option(DummyValue, "enabled", translate("Enabled")) +enabled.rawhtml = true +function enabled.cfgvalue(self, s) + if self.map:get(s, "enabled") == "1" then + return translate("Yes") + else + return translate("No") + end +end + +track_method = mwan_interface:option(DummyValue, "track_method", translate("Tracking method")) +track_method.rawhtml = true +function track_method.cfgvalue(self, s) + local tracked = self.map:get(s, "track_ip") + if tracked then + return self.map:get(s, "track_method") or "ping" + else + return "—" + end +end + +reliability = mwan_interface:option(DummyValue, "reliability", translate("Tracking reliability")) +reliability.rawhtml = true +function reliability.cfgvalue(self, s) + local tracked = self.map:get(s, "track_ip") + if tracked then + return self.map:get(s, "reliability") or "1" + else + return "—" + end +end + +interval = mwan_interface:option(DummyValue, "interval", translate("Ping interval")) +interval.rawhtml = true +function interval.cfgvalue(self, s) + local tracked = self.map:get(s, "track_ip") + if tracked then + local intervalValue = self.map:get(s, "interval") + if intervalValue then + return intervalValue .. "s" + else + return "5s" + end + else + return "—" + end +end + +down = mwan_interface:option(DummyValue, "down", translate("Interface down")) +down.rawhtml = true +function down.cfgvalue(self, s) + local tracked = self.map:get(s, "track_ip") + if tracked then + return self.map:get(s, "down") or "3" + else + return "—" + end +end + +up = mwan_interface:option(DummyValue, "up", translate("Interface up")) +up.rawhtml = true +function up.cfgvalue(self, s) + local tracked = self.map:get(s, "track_ip") + if tracked then + return self.map:get(s, "up") or "3" + else + return "—" + end +end + +metric = mwan_interface:option(DummyValue, "metric", translate("Metric")) +metric.rawhtml = true +function metric.cfgvalue(self, s) + local uci = uci.cursor(nil, "/var/state") + local metric = uci:get("network", s, "metric") + if metric then + return metric + else + return "—" + end +end + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua new file mode 100644 index 0000000000..a6570e341c --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/interfaceconfig.lua @@ -0,0 +1,230 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" +arg[1] = arg[1] or "" + + +m5 = Map("mwan3", translatef("MWAN Interface Configuration - %s", arg[1])) + m5.redirect = dsp.build_url("admin", "network", "mwan", "interface") + +mwan_interface = m5:section(NamedSection, arg[1], "interface", "") +mwan_interface.addremove = false +mwan_interface.dynamic = false + +enabled = mwan_interface:option(ListValue, "enabled", translate("Enabled")) +enabled.default = "1" +enabled:value("1", translate("Yes")) +enabled:value("0", translate("No")) + +initial_state = mwan_interface:option(ListValue, "initial_state", translate("Initial state"), + translate("Expect interface state on up event")) +initial_state.default = "online" +initial_state:value("online", translate("Online")) +initial_state:value("offline", translate("Offline")) + +family = mwan_interface:option(ListValue, "family", translate("Internet Protocol")) +family.default = "ipv4" +family:value("ipv4", translate("IPv4")) +family:value("ipv6", translate("IPv6")) + +track_ip = mwan_interface:option(DynamicList, "track_ip", translate("Tracking hostname or IP address"), + translate("This hostname or IP address will be pinged to determine if the link is up or down. Leave blank to assume interface is always online")) +track_ip.datatype = "host" + +track_method = mwan_interface:option(ListValue, "track_method", translate("Tracking method")) +track_method.default = "ping" +track_method:value("ping") +track_method:value("arping") +track_method:value("httping") + +reliability = mwan_interface:option(Value, "reliability", translate("Tracking reliability"), + translate("Acceptable values: 1-100. This many Tracking IP addresses must respond for the link to be deemed up")) +reliability.datatype = "range(1, 100)" +reliability.default = "1" + +count = mwan_interface:option(ListValue, "count", translate("Ping count")) +count.default = "1" +count:value("1") +count:value("2") +count:value("3") +count:value("4") +count:value("5") + +size = mwan_interface:option(Value, "size", translate("Ping size")) +size.default = "56" +size:depends("track_method", "ping") +size:value("8") +size:value("24") +size:value("56") +size:value("120") +size:value("248") +size:value("504") +size:value("1016") +size:value("1472") +size:value("2040") +size.datatype = "range(1, 65507)" +size.rmempty = false +size.optional = false + +check_quality = mwan_interface:option(Flag, "check_quality", translate("Check link quality")) +check_quality:depends("track_method", "ping") +check_quality.default = false + +failure_latency = mwan_interface:option(Value, "failure_latency", translate("Max packet latency [ms]")) +failure_latency:depends("check_quality", 1) +failure_latency.default = "1000" +failure_latency:value("25") +failure_latency:value("50") +failure_latency:value("75") +failure_latency:value("100") +failure_latency:value("150") +failure_latency:value("200") +failure_latency:value("250") +failure_latency:value("300") + +failure_loss = mwan_interface:option(Value, "failure_loss", translate("Max packet loss [%]")) +failure_loss:depends("check_quality", 1) +failure_loss.default = "20" +failure_loss:value("2") +failure_loss:value("5") +failure_loss:value("10") +failure_loss:value("20") +failure_loss:value("25") + +recovery_latency = mwan_interface:option(Value, "recovery_latency", translate("Min packet latency [ms]")) +recovery_latency:depends("check_quality", 1) +recovery_latency.default = "500" +recovery_latency:value("25") +recovery_latency:value("50") +recovery_latency:value("75") +recovery_latency:value("100") +recovery_latency:value("150") +recovery_latency:value("200") +recovery_latency:value("250") +recovery_latency:value("300") + +recovery_loss = mwan_interface:option(Value, "recovery_loss", translate("Min packet loss [%]")) +recovery_loss:depends("check_quality", 1) +recovery_loss.default = "5" +recovery_loss:value("2") +recovery_loss:value("5") +recovery_loss:value("10") +recovery_loss:value("20") +recovery_loss:value("25") + +timeout = mwan_interface:option(ListValue, "timeout", translate("Ping timeout")) +timeout.default = "2" +timeout:value("1", translatef("%d second", 1)) +timeout:value("2", translatef("%d seconds", 2)) +timeout:value("3", translatef("%d seconds", 3)) +timeout:value("4", translatef("%d seconds", 4)) +timeout:value("5", translatef("%d seconds", 5)) +timeout:value("6", translatef("%d seconds", 6)) +timeout:value("7", translatef("%d seconds", 7)) +timeout:value("8", translatef("%d seconds", 8)) +timeout:value("9", translatef("%d seconds", 9)) +timeout:value("10", translatef("%d seconds", 10)) + +interval = mwan_interface:option(ListValue, "interval", translate("Ping interval")) +interval.default = "5" +interval:value("1", translatef("%d second", 1)) +interval:value("3", translatef("%d seconds", 3)) +interval:value("5", translatef("%d seconds", 5)) +interval:value("10", translatef("%d seconds", 10)) +interval:value("20", translatef("%d seconds", 20)) +interval:value("30", translatef("%d seconds", 30)) +interval:value("60", translatef("%d minute", 1)) +interval:value("300", translatef("%d minutes", 5)) +interval:value("600", translatef("%d minutes", 10)) +interval:value("900", translatef("%d minutes", 15)) +interval:value("1800", translatef("%d minutes", 30)) +interval:value("3600", translatef("%d hour", 1)) + +failure = mwan_interface:option(Value, "failure_interval", translate("Failure interval"), + translate("Ping interval during failure detection")) +failure.default = "5" +failure:value("1", translatef("%d second", 1)) +failure:value("3", translatef("%d seconds", 3)) +failure:value("5", translatef("%d seconds", 5)) +failure:value("10", translatef("%d seconds", 10)) +failure:value("20", translatef("%d seconds", 20)) +failure:value("30", translatef("%d seconds", 30)) +failure:value("60", translatef("%d minute", 1)) +failure:value("300", translatef("%d minutes", 5)) +failure:value("600", translatef("%d minutes", 10)) +failure:value("900", translatef("%d minutes", 15)) +failure:value("1800", translatef("%d minutes", 30)) +failure:value("3600", translatef("%d hour", 1)) + +keep_failure = mwan_interface:option(Flag, "keep_failure_interval", translate("Keep failure interval"), + translate("Keep ping failure interval during failure state")) +keep_failure.default = keep_failure.disabled + +recovery = mwan_interface:option(Value, "recovery_interval", translate("Recovery interval"), + translate("Ping interval during failure recovering")) +recovery.default = "5" +recovery:value("1", translatef("%d second", 1)) +recovery:value("3", translatef("%d seconds", 3)) +recovery:value("5", translatef("%d seconds", 5)) +recovery:value("10", translatef("%d seconds", 10)) +recovery:value("20", translatef("%d seconds", 20)) +recovery:value("30", translatef("%d seconds", 30)) +recovery:value("60", translatef("%d minute", 1)) +recovery:value("300", translatef("%d minutes", 5)) +recovery:value("600", translatef("%d minutes", 10)) +recovery:value("900", translatef("%d minutes", 15)) +recovery:value("1800", translatef("%d minutes", 30)) +recovery:value("3600", translatef("%d hour", 1)) + +down = mwan_interface:option(ListValue, "down", translate("Interface down"), + translate("Interface will be deemed down after this many failed ping tests")) +down.default = "3" +down:value("1") +down:value("2") +down:value("3") +down:value("4") +down:value("5") +down:value("6") +down:value("7") +down:value("8") +down:value("9") +down:value("10") + +up = mwan_interface:option(ListValue, "up", translate("Interface up"), + translate("Downed interface will be deemed up after this many successful ping tests")) +up.default = "3" +up:value("1") +up:value("2") +up:value("3") +up:value("4") +up:value("5") +up:value("6") +up:value("7") +up:value("8") +up:value("9") +up:value("10") + +flush = mwan_interface:option(ListValue, "flush_conntrack", translate("Flush conntrack table"), + translate("Flush global firewall conntrack table on interface events")) +flush.default = "never" +flush:value("ifup", translate("ifup")) +flush:value("ifdown", translate("ifdown")) +flush:value("never", translate("never")) +flush:value("always", translate("always")) + +metric = mwan_interface:option(DummyValue, "metric", translate("Metric"), + translate("This displays the metric assigned to this interface in /etc/config/network")) +metric.rawhtml = true +function metric.cfgvalue(self, s) + local uci = require "luci.model.uci".cursor(nil, "/var/state") + local metric = uci:get("network", arg[1], "metric") + if metric then + return metric + else + return "—" + end +end + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua new file mode 100644 index 0000000000..9b4ab102d5 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/member.lua @@ -0,0 +1,44 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" + + +m5 = Map("mwan3", translate("MWAN - Members")) + +mwan_member = m5:section(TypedSection, "member", nil, + translate("Members are profiles attaching a metric and weight to an MWAN interface<br />" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" .. + "Members may not share the same name as configured interfaces, policies or rules")) +mwan_member.addremove = true +mwan_member.dynamic = false +mwan_member.sectionhead = translate("Member") +mwan_member.sortable = true +mwan_member.template = "cbi/tblsection" +mwan_member.extedit = dsp.build_url("admin", "network", "mwan", "member", "%s") +function mwan_member.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(dsp.build_url("admin", "network", "mwan", "member", section)) +end + +interface = mwan_member:option(DummyValue, "interface", translate("Interface")) +interface.rawhtml = true +function interface.cfgvalue(self, s) + return self.map:get(s, "interface") or "—" +end + +metric = mwan_member:option(DummyValue, "metric", translate("Metric")) +metric.rawhtml = true +function metric.cfgvalue(self, s) + return self.map:get(s, "metric") or "1" +end + +weight = mwan_member:option(DummyValue, "weight", translate("Weight")) +weight.rawhtml = true +function weight.cfgvalue(self, s) + return self.map:get(s, "weight") or "1" +end + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua new file mode 100644 index 0000000000..27d9a3e858 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/memberconfig.lua @@ -0,0 +1,31 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" +arg[1] = arg[1] or "" + + +m5 = Map("mwan3", translatef("MWAN Member Configuration - %s", arg[1])) +m5.redirect = dsp.build_url("admin", "network", "mwan", "member") + +mwan_member = m5:section(NamedSection, arg[1], "member", "") +mwan_member.addremove = false +mwan_member.dynamic = false + +interface = mwan_member:option(Value, "interface", translate("Interface")) +m5.uci:foreach("mwan3", "interface", + function(s) + interface:value(s['.name'], s['.name']) + end +) + +metric = mwan_member:option(Value, "metric", translate("Metric"), + translate("Acceptable values: 1-256. Defaults to 1 if not set")) +metric.datatype = "range(1, 256)" + +weight = mwan_member:option(Value, "weight", translate("Weight"), + translate("Acceptable values: 1-1000. Defaults to 1 if not set")) +weight.datatype = "range(1, 1000)" + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/notify.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/notify.lua new file mode 100644 index 0000000000..4c6e21003e --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/notify.lua @@ -0,0 +1,45 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +local fs = require "nixio.fs" +local ut = require "luci.util" +script = "/etc/mwan3.user" + + +m5 = SimpleForm("luci", translate("MWAN - Notification")) + +f = m5:section(SimpleSection, nil, + translate("This section allows you to modify the content of \"/etc/mwan3.user\".<br />" .. + "The file is also preserved during sysupgrade.<br />" .. + "<br />" .. + "Notes:<br />" .. + "This file is interpreted as a shell script.<br />" .. + "The first line of the script must be "#!/bin/sh" without quotes.<br />" .. + "Lines beginning with # are comments and are not executed.<br />" .. + "Put your custom mwan3 action here, they will<br />" .. + "be executed with each netifd hotplug interface event<br />" .. + "on interfaces for which mwan3 is enabled.<br />" .. + "<br />" .. + "There are three main environment variables that are passed to this script.<br />" .. + "<br />" .. + "$ACTION <br />" .. + "* \"ifup\" Is called by netifd and mwan3track <br />" .. + "* \"ifdown\" Is called by netifd and mwan3track <br />" .. + "* \"connected\" Is only called by mwan3track if tracking was successful <br />" .. + "* \"disconnected\" Is only called by mwan3track if tracking has failed <br />" .. + "$INTERFACE Name of the interface which went up or down (e.g. \"wan\" or \"wwan\")<br />" .. + "$DEVICE Physical device name which interface went up or down (e.g. \"eth0\" or \"wwan0\")<br />" .. + "<br />")) + +t = f:option(TextValue, "lines") +t.rmempty = true +t.rows = 20 +function t.cfgvalue() + return fs.readfile(script) +end +function t.write(self, section, data) + return fs.writefile(script, ut.trim(data:gsub("\r\n", "\n")) .. "\n") +end + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua new file mode 100644 index 0000000000..4543260f6f --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policy.lua @@ -0,0 +1,87 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" +uci = require "uci" + + +function policyCheck() + local policy_error = {} + + uci.cursor():foreach("mwan3", "policy", + function (section) + policy_error[section[".name"]] = false + if string.len(section[".name"]) > 15 then + policy_error[section[".name"]] = true + end + end + ) + + return policy_error +end + +function policyError(policy_error) + local warnings = "" + for i, k in pairs(policy_error) do + if policy_error[i] == true then + warnings = warnings .. string.format("<strong>%s</strong><br />", + translatef("WARNING: Policy %s has exceeding the maximum name of 15 characters", i) + ) + end + end + + return warnings +end + +m5 = Map("mwan3", translate("MWAN - Policies"), + policyError(policyCheck())) + +mwan_policy = m5:section(TypedSection, "policy", nil, + translate("Policies are profiles grouping one or more members controlling how MWAN distributes traffic<br />" .. + "Member interfaces with lower metrics are used first<br />" .. + "Member interfaces with the same metric will be load-balanced<br />" .. + "Load-balanced member interfaces distribute more traffic out those with higher weights<br />" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" .. + "Names must be 15 characters or less<br />" .. + "Policies may not share the same name as configured interfaces, members or rules")) +mwan_policy.addremove = true +mwan_policy.dynamic = false +mwan_policy.sectionhead = translate("Policy") +mwan_policy.sortable = true +mwan_policy.template = "cbi/tblsection" +mwan_policy.extedit = dsp.build_url("admin", "network", "mwan", "policy", "%s") +function mwan_policy.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(dsp.build_url("admin", "network", "mwan", "policy", section)) +end + +use_member = mwan_policy:option(DummyValue, "use_member", translate("Members assigned")) +use_member.rawhtml = true +function use_member.cfgvalue(self, s) + local memberConfig, memberList = self.map:get(s, "use_member"), "" + if memberConfig then + for k,v in pairs(memberConfig) do + memberList = memberList .. v .. "<br />" + end + return memberList + else + return "—" + end +end + +last_resort = mwan_policy:option(DummyValue, "last_resort", translate("Last resort")) +last_resort.rawhtml = true +function last_resort.cfgvalue(self, s) + local action = self.map:get(s, "last_resort") + if action == "blackhole" then + return translate("blackhole (drop)") + elseif action == "default" then + return translate("default (use main routing table)") + else + return translate("unreachable (reject)") + end +end + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua new file mode 100644 index 0000000000..d1a063d093 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/policyconfig.lua @@ -0,0 +1,30 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" +arg[1] = arg[1] or "" + + +m5 = Map("mwan3", translatef("MWAN Policy Configuration - %s", arg[1])) +m5.redirect = dsp.build_url("admin", "network", "mwan", "policy") + +mwan_policy = m5:section(NamedSection, arg[1], "policy", "") +mwan_policy.addremove = false +mwan_policy.dynamic = false + +member = mwan_policy:option(DynamicList, "use_member", translate("Member used")) +m5.uci:foreach("mwan3", "member", + function(s) + member:value(s['.name'], s['.name']) + end +) + +last_resort = mwan_policy:option(ListValue, "last_resort", translate("Last resort"), + translate("When all policy members are offline use this behavior for matched traffic")) +last_resort.default = "unreachable" +last_resort:value("unreachable", translate("unreachable (reject)")) +last_resort:value("blackhole", translate("blackhole (drop)")) +last_resort:value("default", translate("default (use main routing table)")) + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua new file mode 100644 index 0000000000..f0b94bd0bb --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/rule.lua @@ -0,0 +1,104 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" +uci = require "uci" + + +function ruleCheck() + local rule_error = {} + uci.cursor():foreach("mwan3", "rule", + function (section) + rule_error[section[".name"]] = false + local uci = uci.cursor(nil, "/var/state") + local sourcePort = uci:get("mwan3", section[".name"], "src_port") + local destPort = uci:get("mwan3", section[".name"], "dest_port") + if sourcePort ~= nil or destPort ~= nil then + local protocol = uci:get("mwan3", section[".name"], "proto") + if protocol == nil or protocol == "all" then + rule_error[section[".name"]] = true + end + end + end + ) + return rule_error +end + +function ruleWarn(rule_error) + local warnings = "" + for i, k in pairs(rule_error) do + if rule_error[i] == true then + warnings = warnings .. string.format("<strong>%s</strong><br />", + translatef("WARNING: Rule %s have a port configured with no or improper protocol specified!", i) + ) + end + end + + return warnings +end + +m5 = Map("mwan3", translate("MWAN - Rules"), + ruleWarn(ruleCheck()) + ) + +mwan_rule = m5:section(TypedSection, "rule", nil, + translate("Rules specify which traffic will use a particular MWAN policy<br />" .. + "Rules are based on IP address, port or protocol<br />" .. + "Rules are matched from top to bottom<br />" .. + "Rules below a matching rule are ignored<br />" .. + "Traffic not matching any rule is routed using the main routing table<br />" .. + "Traffic destined for known (other than default) networks is handled by the main routing table<br />" .. + "Traffic matching a rule, but all WAN interfaces for that policy are down will be blackholed<br />" .. + "Names may contain characters A-Z, a-z, 0-9, _ and no spaces<br />" .. + "Rules may not share the same name as configured interfaces, members or policies")) +mwan_rule.addremove = true +mwan_rule.anonymous = false +mwan_rule.dynamic = false +mwan_rule.sectionhead = translate("Rule") +mwan_rule.sortable = true +mwan_rule.template = "cbi/tblsection" +mwan_rule.extedit = dsp.build_url("admin", "network", "mwan", "rule", "%s") +function mwan_rule.create(self, section) + TypedSection.create(self, section) + m5.uci:save("mwan3") + luci.http.redirect(dsp.build_url("admin", "network", "mwan", "rule", section)) +end + +src_ip = mwan_rule:option(DummyValue, "src_ip", translate("Source address")) +src_ip.rawhtml = true +function src_ip.cfgvalue(self, s) + return self.map:get(s, "src_ip") or "—" +end + +src_port = mwan_rule:option(DummyValue, "src_port", translate("Source port")) +src_port.rawhtml = true +function src_port.cfgvalue(self, s) + return self.map:get(s, "src_port") or "—" +end + +dest_ip = mwan_rule:option(DummyValue, "dest_ip", translate("Destination address")) +dest_ip.rawhtml = true +function dest_ip.cfgvalue(self, s) + return self.map:get(s, "dest_ip") or "—" +end + +dest_port = mwan_rule:option(DummyValue, "dest_port", translate("Destination port")) +dest_port.rawhtml = true +function dest_port.cfgvalue(self, s) + return self.map:get(s, "dest_port") or "—" +end + +proto = mwan_rule:option(DummyValue, "proto", translate("Protocol")) +proto.rawhtml = true +function proto.cfgvalue(self, s) + return self.map:get(s, "proto") or "all" +end + +use_policy = mwan_rule:option(DummyValue, "use_policy", translate("Policy assigned")) +use_policy.rawhtml = true +function use_policy.cfgvalue(self, s) + return self.map:get(s, "use_policy") or "—" +end + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua new file mode 100644 index 0000000000..84adfcf910 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/model/cbi/mwan/ruleconfig.lua @@ -0,0 +1,63 @@ +-- Copyright 2014 Aedan Renner <chipdankly@gmail.com> +-- Copyright 2018 Florian Eckert <fe@dev.tdt.de> +-- Licensed to the public under the GNU General Public License v2. + +dsp = require "luci.dispatcher" +arg[1] = arg[1] or "" + + +m5 = Map("mwan3", translatef("MWAN Rule Configuration - %s", arg[1])) +m5.redirect = dsp.build_url("admin", "network", "mwan", "rule") + +mwan_rule = m5:section(NamedSection, arg[1], "rule", "") +mwan_rule.addremove = false +mwan_rule.dynamic = false + +src_ip = mwan_rule:option(Value, "src_ip", translate("Source address"), + translate("Supports CIDR notation (eg \"192.168.100.0/24\") without quotes")) +src_ip.datatype = ipaddr + +src_port = mwan_rule:option(Value, "src_port", translate("Source port"), + translate("May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes")) + +dest_ip = mwan_rule:option(Value, "dest_ip", translate("Destination address"), + translate("Supports CIDR notation (eg \"192.168.100.0/24\") without quotes")) +dest_ip.datatype = ipaddr + +dest_port = mwan_rule:option(Value, "dest_port", translate("Destination port"), + translate("May be entered as a single or multiple port(s) (eg \"22\" or \"80,443\") or as a portrange (eg \"1024:2048\") without quotes")) + +proto = mwan_rule:option(Value, "proto", translate("Protocol"), + translate("View the content of /etc/protocols for protocol description")) +proto.default = "all" +proto.rmempty = false +proto:value("all") +proto:value("tcp") +proto:value("udp") +proto:value("icmp") +proto:value("esp") + +sticky = mwan_rule:option(ListValue, "sticky", translate("Sticky"), + translate("Traffic from the same source IP address that previously matched this rule within the sticky timeout period will use the same WAN interface")) +sticky.default = "0" +sticky:value("1", translate("Yes")) +sticky:value("0", translate("No")) + +timeout = mwan_rule:option(Value, "timeout", translate("Sticky timeout"), + translate("Seconds. Acceptable values: 1-1000000. Defaults to 600 if not set")) +timeout.datatype = "range(1, 1000000)" + +ipset = mwan_rule:option(Value, "ipset", translate("IPset"), + translate("Name of IPset rule. Requires IPset rule in /etc/dnsmasq.conf (eg \"ipset=/youtube.com/youtube\")")) + +policy = mwan_rule:option(Value, "use_policy", translate("Policy assigned")) +m5.uci:foreach("mwan3", "policy", + function(s) + policy:value(s['.name'], s['.name']) + end +) +policy:value("unreachable", translate("unreachable (reject)")) +policy:value("blackhole", translate("blackhole (drop)")) +policy:value("default", translate("default (use main routing table)")) + +return m5 diff --git a/applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm b/applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm new file mode 100644 index 0000000000..e4b3c06999 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/admin_status/index/mwan.htm @@ -0,0 +1,3 @@ +<%if require("luci.sys").init.enabled("mwan3") then%> +<%+mwan/overview_status_interface%> +<%end%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm b/applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm new file mode 100644 index 0000000000..49d120c1ae --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm @@ -0,0 +1,75 @@ +<%# + Copyright 2014 Aedan Renner <chipdankly@gmail.com> + Copyright 2018 Florian Eckert <fe@dev.tdt.de> + Licensed to the public under the GNU General Public License v2. +-%> + +<script type="text/javascript">//<![CDATA[ +XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_status")%>', null, + function(x, status) + { + var statusDiv = document.getElementById('mwan_status_text'); + if (status.interfaces) + { + var statusview = ''; + for ( var iface in status.interfaces) + { + var state = ''; + var css = '' + switch (status.interfaces[iface].status) + { + case 'online': + state = '<%:Online%>'; + css = 'success'; + break; + case 'offline': + state = '<%:Offline%>'; + css = 'danger'; + break; + default: + state = '<%:Disabled%>'; + css = 'warning'; + break; + } + statusview += String.format( + '<div class="alert-message %s">', + css + ); + statusview += String.format( + '<div><strong>Interface: </strong>%s</div>', + iface + ); + statusview += String.format( + '<div><strong>Status: </strong>%s</div>', + state + ); + statusview += '</div>' + } + statusDiv.innerHTML = statusview; + } + else + { + statusDiv.innerHTML = '<strong><%:No MWAN interfaces found%></strong>'; + } + } + ); +//]]></script> + +<style type="text/css"> + #mwan_status_text > div { + display: inline-block; + margin: 1rem; + padding: 1rem; + width: 10rem; + float: left; + line-height: 125%; + } +</style> + +<fieldset id="interface_field" class="cbi-section"> + <legend><%:MWAN Interfaces%></legend> + <div id="mwan_status_text"> + <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> + <%:Collecting data...%> + </div> +</fieldset> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm new file mode 100644 index 0000000000..12700c4a36 --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm @@ -0,0 +1,39 @@ +<%# + Copyright 2014 Aedan Renner <chipdankly@gmail.com> + Copyright 2018 Florian Eckert <fe@dev.tdt.de> + Licensed to the public under the GNU General Public License v2. +-%> + +<%+header%> + +<ul class="cbi-tabmenu"> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li> + <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li> +</ul> + +<script type="text/javascript">//<![CDATA[ + XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "detailed_status")%>', null, + function(x) + { + var output = document.getElementById('diag-rc-output'); + output.innerHTML = String.format('<pre>%h</pre>', x.responseText); + } + ); +//]]></script> + +<div class="cbi-map"> + <h2 name="content"><%:MWAN Status - Detail%></h2> + <%if not require("luci.sys").init.enabled("mwan3") then%> + <div><strong><%:INFO: MWAN not running%></strong></div> + <%end%> + <fieldset class="cbi-section"> + <span id="diag-rc-output"> + <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" /> + <%:Collecting data...%> + </span> + </fieldset> +</div> + +<%+footer%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm new file mode 100644 index 0000000000..454adcc1cd --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm @@ -0,0 +1,90 @@ +<%# + Copyright 2014 Aedan Renner <chipdankly@gmail.com> + Copyright 2018 Florian Eckert <fe@dev.tdt.de> + Licensed to the public under the GNU General Public License v2. +-%> + +<%+header%> + +<ul class="cbi-tabmenu"> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li> + <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li> +</ul> + +<% + local uci = require "luci.model.uci" + + local iface = {} + + uci.cursor():foreach("mwan3", "interface", + function (section) + table.insert(iface, section[".name"]) + end + ) +%> + +<script type="text/javascript">//<![CDATA[ + var stxhr = new XHR(); + + function update_status(iface, task) + { + var output = document.getElementById('diag-rc-output'); + + output.innerHTML = + '<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />' + + "<%:Waiting for command to complete...%>" + ; + + output.parentNode.style.display = 'block'; + output.style.display = 'inline'; + + stxhr.post('<%=url('admin/status/mwan')%>/diagnostics_display' + '/' + iface + '/' + task, { token: '<%=token%>' }, + function(x) + { + output.innerHTML = String.format('<pre>%h</pre>', x.responseText); + } + ); + } +//]]></script> + +<form method="post" action="<%=url('admin/network/diagnostics')%>"> + <div class="cbi-map"> + <h2 name="content"><%:MWAN Status - Diagnostics%></h2> + <%if not require("luci.sys").init.enabled("mwan3") then%> + <div><strong><%:INFO: MWAN not running%></strong></div> + <%end%> + <fieldset class="cbi-section"> + <br /> + + <div style="width:30%; float:left"> + <label class="cbi-value-title"><%:Interface%></label> + <select name="iface" style="width:auto"> + <% for _, z in ipairs(iface) do -%><option value="<%=z%>"><%=z%></option><%- end %> + </select> + </div> + + <div style="width:30%; float:left"> + <label class="cbi-value-title"><%:Task%></label> + <select name="task" style="width:auto"> + <option value="ping_gateway"><%:Ping default gateway%></option> + <option value="ping_trackips"><%:Ping tracking IP%></option> + <option value="check_rules"><%:Check IP rules%></option> + <option value="check_routes"><%:Check routing table%></option> + <option value="hotplug_ifup"><%:Hotplug ifup%></option> + <option value="hotplug_ifdown"><%:Hotplug ifdown%></option> + </select> + </div> + + <div style="width:30%; float:left"> + <input type="button" value="<%:Execute%>" class="cbi-button cbi-button-apply" onclick="update_status(this.form.iface.value, this.form.task.value)"/> + </div> + </fieldset> + </div> + <fieldset class="cbi-section" style="display:none"> + <span id="diag-rc-output"></span> + </fieldset> +</form> + +<%+footer%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm new file mode 100644 index 0000000000..962cde521d --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm @@ -0,0 +1,20 @@ +<%# + Copyright 2014 Aedan Renner <chipdankly@gmail.com> + Copyright 2018 Florian Eckert <fe@dev.tdt.de> + Licensed to the public under the GNU General Public License v2. +-%> + +<%+header%> + +<ul class="cbi-tabmenu"> + <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li> +</ul> + + +<div class="cbi-map"> + <%+mwan/overview_status_interface%> +</div> +<%+footer%> diff --git a/applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm b/applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm new file mode 100644 index 0000000000..a20516bd2a --- /dev/null +++ b/applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm @@ -0,0 +1,39 @@ +<%# + Copyright 2014 Aedan Renner <chipdankly@gmail.com> + Copyright 2018 Florian Eckert <fe@dev.tdt.de> + Licensed to the public under the GNU General Public License v2. +-%> + +<%+header%> + +<ul class="cbi-tabmenu"> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/overview")%>"><%:Interface%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/detail")%>"><%:Detail%></a></li> + <li class="cbi-tab-disabled"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/diagnostics")%>"><%:Diagnostics%></a></li> + <li class="cbi-tab"><a href="<%=luci.dispatcher.build_url("admin/status/mwan/troubleshooting")%>"><%:Troubleshooting%></a></li> +</ul> + +<script type="text/javascript">//<![CDATA[ + XHR.poll(15, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "troubleshooting_display")%>', null, + function(x) + { + var output = document.getElementById('diag-rc-output'); + output.innerHTML = String.format('<pre>%h</pre>', x.responseText); + } + ); +//]]></script> + +<div class="cbi-map"> + <h2 name="content"><%:MWAN Status - Troubleshooting%></h2> + <%if not require("luci.sys").init.enabled("mwan3") then%> + <div><strong><%:INFO: MWAN not running%></strong></div> + <%end%> + <fieldset class="cbi-section"> + <span id="diag-rc-output"> + <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" /> + <%:Collecting data...%> + </span> + </fieldset> +</div> + +<%+footer%> |