diff options
24 files changed, 936 insertions, 475 deletions
diff --git a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-advanced.lua b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-advanced.lua index 9a37ba8022..25d1481f8a 100644 --- a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-advanced.lua +++ b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-advanced.lua @@ -158,10 +158,10 @@ local knownParams = { "script_security", { 0, 1, 2, 3 }, translate("Policy level over usage of external programs and scripts") }, - { Value, - "config", - "/etc/openvpn/ovpn-file.ovpn", - translate("Local OVPN configuration file") }, + { ListValue, + "compress", + { "lzo", "lz4" }, + translate("Enable a compression algorithm") }, } }, { "Networking", { @@ -238,6 +238,10 @@ local knownParams = { "route_nopull", 0, translate("Don't pull routes automatically") }, + { Flag, + "allow_recursive_routing", + 0, + translate("Don't drop incoming tun packets with same destination as host") }, { ListValue, "mtu_disc", { "yes", "maybe", "no" }, @@ -542,6 +546,10 @@ local knownParams = { { "", "local", "def1", "local def1" }, translate("Automatically redirect default route"), { client="1" } }, + { Value, + "verify_client_cert", + { "none", "optional", "require" }, + translate("Specify whether the client is required to supply a valid certificate") }, } }, { "Cryptography", { @@ -557,7 +565,51 @@ local knownParams = { -- parse { Value, "cipher", - "BF-CBC", + { + "AES-128-CBC", + "AES-128-CFB", + "AES-128-CFB1", + "AES-128-CFB8", + "AES-128-GCM", + "AES-128-OFB", + "AES-192-CBC", + "AES-192-CFB", + "AES-192-CFB1", + "AES-192-CFB8", + "AES-192-GCM", + "AES-192-OFB", + "AES-256-CBC", + "AES-256-CFB", + "AES-256-CFB1", + "AES-256-CFB8", + "AES-256-GCM", + "AES-256-OFB", + "BF-CBC", + "BF-CFB", + "BF-OFB", + "CAST5-CBC", + "CAST5-CFB", + "CAST5-OFB", + "DES-CBC", + "DES-CFB", + "DES-CFB1", + "DES-CFB8", + "DES-EDE-CBC", + "DES-EDE-CFB", + "DES-EDE-OFB", + "DES-EDE3-CBC", + "DES-EDE3-CFB", + "DES-EDE3-CFB1", + "DES-EDE3-CFB8", + "DES-EDE3-OFB", + "DES-OFB", + "DESX-CBC", + "RC2-40-CBC", + "RC2-64-CBC", + "RC2-CBC", + "RC2-CFB", + "RC2-OFB" + }, translate("Encryption cipher for packets") }, -- parse { Value, @@ -695,6 +747,14 @@ local knownParams = { "key_direction", { 0, 1 }, translate("The key direction for 'tls-auth' and 'secret' options") }, + { Flag, + "ncp_disable", + 0, + translate("This completely disables cipher negotiation") }, + { Value, + "ncp_ciphers", + "AES-256-GCM:AES-128-GCM", + translate("Restrict the allowed ciphers to be negotiated") }, } } } diff --git a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-basic.lua b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-basic.lua index 3be274dc8b..3e9137baeb 100644 --- a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-basic.lua +++ b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-basic.lua @@ -87,10 +87,6 @@ local basicParams = { "key", "/etc/easy-rsa/keys/some-client.key", translate("Local private key") }, - { Value, - "config", - "/etc/openvpn/ovpn-file.ovpn", - translate("Local OVPN configuration file") }, } diff --git a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-file.lua b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-file.lua index 6878275d78..9d50601b1f 100644 --- a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-file.lua +++ b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn-file.lua @@ -1,10 +1,11 @@ -- Licensed to the public under the Apache License 2.0. -local ip = require("luci.ip") -local fs = require("nixio.fs") -local util = require("luci.util") -local uci = require("luci.model.uci").cursor() -local cfg_file = uci:get("openvpn", arg[1], "config") +local ip = require("luci.ip") +local fs = require("nixio.fs") +local util = require("luci.util") +local uci = require("luci.model.uci").cursor() +local cfg_file = uci:get("openvpn", arg[1], "config") +local auth_file = cfg_file:match("(.+)%..+").. ".auth" local m = Map("openvpn") @@ -36,25 +37,45 @@ f:append(Template("openvpn/ovpn_css")) f.submit = translate("Save") f.reset = false -s = f:section(SimpleSection, nil, translatef("This form allows you to modify the content of the OVPN config file (%s). ", cfg_file)) -file = s:option(TextValue, "data") +s = f:section(SimpleSection, nil, translatef("Section to modify the OVPN config file (%s)", cfg_file)) +file = s:option(TextValue, "data1") file.datatype = "string" file.rows = 20 -file.rmempty = true function file.cfgvalue() return fs.readfile(cfg_file) or "" end -function file.write(self, section, data) - return fs.writefile(cfg_file, "\n" .. util.trim(data:gsub("\r\n", "\n")) .. "\n") +function file.write(self, section, data1) + return fs.writefile(cfg_file, "\n" .. util.trim(data1:gsub("\r\n", "\n")) .. "\n") end function file.remove(self, section, value) return fs.writefile(cfg_file, "") end -function s.handle(self, state, data) +function s.handle(self, state, data1) + return true +end + +s = f:section(SimpleSection, nil, translatef("Section to add an optional 'auth-user-pass' file with your credentials (%s)", auth_file)) +file = s:option(TextValue, "data2") +file.datatype = "string" +file.rows = 5 + +function file.cfgvalue() + return fs.readfile(auth_file) or "" +end + +function file.write(self, section, data2) + return fs.writefile(auth_file, util.trim(data2:gsub("\r\n", "\n")) .. "\n") +end + +function file.remove(self, section, value) + return fs.writefile(auth_file, "") +end + +function s.handle(self, state, data2) return true end diff --git a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn.lua b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn.lua index ad607ae6c2..41266d860e 100644 --- a/applications/luci-app-openvpn/luasrc/model/cbi/openvpn.lua +++ b/applications/luci-app-openvpn/luasrc/model/cbi/openvpn.lua @@ -69,10 +69,14 @@ function s.create(self, name) local options = uci:get_all("openvpn_recipes", recipe) for k, v in pairs(options) do if k ~= "_role" and k ~= "_description" then + if type(v) == "boolean" then + v = v and "1" or "0" + end uci:set("openvpn", name, k, v) end end uci:save("openvpn") + uci:commit("openvpn") if extedit then luci.http.redirect( self.extedit:format(name) ) end @@ -80,10 +84,23 @@ function s.create(self, name) elseif #name > 0 then self.invalid_cts = true end - return 0 end +function s.remove(self, name) + local cfg_file = "/etc/openvpn/" ..name.. ".ovpn" + local auth_file = "/etc/openvpn/" ..name.. ".auth" + if fs.access(cfg_file) then + fs.unlink(cfg_file) + end + if fs.access(auth_file) then + fs.unlink(auth_file) + end + uci:delete("openvpn", name) + uci:save("openvpn") + uci:commit("openvpn") +end + s:option( Flag, "enabled", translate("Enabled") ) local active = s:option( DummyValue, "_active", translate("Started") ) @@ -124,12 +141,30 @@ end local port = s:option( DummyValue, "port", translate("Port") ) function port.cfgvalue(self, section) local val = AbstractValue.cfgvalue(self, section) + if not val then + local file_cfg = self.map:get(section, "config") + if file_cfg and fs.access(file_cfg) then + val = sys.exec("awk '{if(match(tolower($1),/^port$/)&&match($2,/[0-9]+/)){cnt++;printf $2;exit}}END{if(cnt==0)printf \"-\"}' " ..file_cfg) + if val == "-" then + val = sys.exec("awk '{if(match(tolower($1),/^remote$/)&&match($3,/[0-9]+/)){cnt++;printf $3;exit}}END{if(cnt==0)printf \"-\"}' " ..file_cfg) + end + end + end return val or "-" end local proto = s:option( DummyValue, "proto", translate("Protocol") ) function proto.cfgvalue(self, section) local val = AbstractValue.cfgvalue(self, section) + if not val then + local file_cfg = self.map:get(section, "config") + if file_cfg and fs.access(file_cfg) then + val = sys.exec("awk '{if(match(tolower($1),/^proto$/)&&match(tolower($2),/^udp[46]*$|^tcp[46]*-server$|^tcp[46]*-client$/)){cnt++;printf tolower($2);exit}}END{if(cnt==0)printf \"-\"}' " ..file_cfg) + if val == "-" then + val = sys.exec("awk '{if(match(tolower($1),/^remote$/)&&match(tolower($4),/^udp[46]*$|^tcp[46]*-server$|^tcp[46]*-client$/)){cnt++;printf $4;exit}}END{if(cnt==0)printf \"-\"}' " ..file_cfg) + end + end + end return val or "-" end diff --git a/applications/luci-app-openvpn/luasrc/view/openvpn/cbi-select-input-add.htm b/applications/luci-app-openvpn/luasrc/view/openvpn/cbi-select-input-add.htm index 09da2eb22d..e75bfda900 100644 --- a/applications/luci-app-openvpn/luasrc/view/openvpn/cbi-select-input-add.htm +++ b/applications/luci-app-openvpn/luasrc/view/openvpn/cbi-select-input-add.htm @@ -3,7 +3,7 @@ //<![CDATA[ function vpn_add() { - var vpn_name = div_add.querySelector("#instance_name1").value.replace(/[^\x00-\x7F]|[\s!@#$%^&*()+=\[\]{};':"\\|,<>\/?]/g,''); + var vpn_name = div_add.querySelector("#instance_name1").value.replace(/[^\x00-\x7F]|[\s!@#$%^&*()\-+=\[\]{};':"\\|,<>\/?]/g,''); var vpn_template = div_add.querySelector("#instance_template").value; var form = document.getElementsByName('cbi')[0]; @@ -31,7 +31,7 @@ function vpn_upload() { - var vpn_name = div_upload.querySelector("#instance_name2").value.replace(/[^\x00-\x7F]|[\s!@#$%^&*()+=\[\]{};':"\\|,<>\/?]/g,''); + var vpn_name = div_upload.querySelector("#instance_name2").value.replace(/[^\x00-\x7F]|[\s!@#$%^&*()\-+=\[\]{};':"\\|,<>\/?]/g,''); var vpn_file = document.getElementById("ovpn_file").value; var form = document.getElementsByName('cbi')[0]; @@ -77,10 +77,10 @@ <div class="table cbi-section-table"> <h4><%:Template based configuration%></h4> <div class="tr cbi-section-table-row" id="div_add"> - <div class="td"> + <div class="td left"> <input type="text" maxlength="20" placeholder="Instance name" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.text" id="instance_name1" /> </div> - <div class="td"> + <div class="td left"> <select id="instance_template" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.select"> <option value="" selected="selected" disabled="disabled"><%:Select template ...%></option> <%- for k, v in luci.util.kspairs(self.add_select_options) do %> @@ -88,19 +88,19 @@ <% end -%> </select> </div> - <div class="td"> + <div class="td left"> <input class="cbi-button cbi-button-add" type="submit" onclick="vpn_add(); return false;" value="<%:Add%>" title="<%:Add template based configuration%>" /><br /> </div> </div> <h4><%:OVPN configuration file upload%></h4> <div class="tr cbi-section-table-row" id="div_upload"> - <div class="td"> + <div class="td left"> <input type="text" maxlength="20" placeholder="Instance name" name="instance_name2" id="instance_name2" /> </div> - <div class="td"> + <div class="td left"> <input type="file" name="ovpn_file" id="ovpn_file" accept="application/x-openvpn-profile,.ovpn" /> </div> - <div class="td"> + <div class="td left"> <input class="cbi-button cbi-button-add" type="submit" onclick="vpn_upload(); return false;" value="<%:Upload%>" title="<%:Upload ovpn file%>" /> </div> </div> diff --git a/applications/luci-app-openvpn/luasrc/view/openvpn/ovpn_css.htm b/applications/luci-app-openvpn/luasrc/view/openvpn/ovpn_css.htm index c7062b8d7a..55c0a543fc 100644 --- a/applications/luci-app-openvpn/luasrc/view/openvpn/ovpn_css.htm +++ b/applications/luci-app-openvpn/luasrc/view/openvpn/ovpn_css.htm @@ -10,12 +10,6 @@ border: 0px; text-align: left; } - .td - { - text-align: left; - border-top: 0px; - margin: 5px; - } .vpn-output { box-shadow: none; diff --git a/applications/luci-app-openvpn/luasrc/view/openvpn/pageswitch.htm b/applications/luci-app-openvpn/luasrc/view/openvpn/pageswitch.htm index 17beef0d39..c1fe05215a 100644 --- a/applications/luci-app-openvpn/luasrc/view/openvpn/pageswitch.htm +++ b/applications/luci-app-openvpn/luasrc/view/openvpn/pageswitch.htm @@ -11,17 +11,11 @@ <a href="<%=url('admin/services/openvpn')%>"><%:Overview%></a> » <%=luci.i18n.translatef("Instance \"%s\"", self.instance)%> </h3> - <% if self.mode == "file" then %> - <a href="<%=url('admin/services/openvpn/basic', self.instance)%>"><%:Switch to basic configuration%> »</a><p/> - <a href="<%=url('admin/services/openvpn/advanced', self.instance, "Service")%>"><%:Switch to advanced configuration%> »</a> - <hr /> - <% elseif self.mode == "basic" then %> + <% if self.mode == "basic" then %> <a href="<%=url('admin/services/openvpn/advanced', self.instance, "Service")%>"><%:Switch to advanced configuration%> »</a><p/> - <a href="<%=url('admin/services/openvpn/file', self.instance)%>"><%:Switch to file based configuration%> »</a> <hr /> <% elseif self.mode == "advanced" then %> <a href="<%=url('admin/services/openvpn/basic', self.instance)%>"><%:Switch to basic configuration%> »</a><p/> - <a href="<%=url('admin/services/openvpn/file', self.instance)%>"><%:Switch to file based configuration%> »</a> <hr /> <%:Configuration category%>: <% for i, c in ipairs(self.categories) do %> diff --git a/modules/luci-base/htdocs/luci-static/resources/luci.js b/modules/luci-base/htdocs/luci-static/resources/luci.js index dcda941f7b..04c460182f 100644 --- a/modules/luci-base/htdocs/luci-static/resources/luci.js +++ b/modules/luci-base/htdocs/luci-static/resources/luci.js @@ -74,6 +74,7 @@ return XHR.get(url, data, cb); }, + stop: function(entry) { XHR.stop(entry) }, halt: function() { XHR.halt() }, run: function() { XHR.run() }, @@ -310,7 +311,10 @@ function LuCI(env) { this.env = env; - modalDiv = document.body.appendChild(this.dom.create('div', { id: 'modal_overlay' }, this.dom.create('div', { class: 'modal' }))); + modalDiv = document.body.appendChild( + this.dom.create('div', { id: 'modal_overlay' }, + this.dom.create('div', { class: 'modal', role: 'dialog', 'aria-modal': true }))); + tooltipDiv = document.body.appendChild(this.dom.create('div', { class: 'cbi-tooltip' })); document.addEventListener('mouseover', this.showTooltip.bind(this), true); diff --git a/modules/luci-base/luasrc/dispatcher.lua b/modules/luci-base/luasrc/dispatcher.lua index c4066a2592..d85cb58243 100644 --- a/modules/luci-base/luasrc/dispatcher.lua +++ b/modules/luci-base/luasrc/dispatcher.lua @@ -40,6 +40,28 @@ function build_url(...) return table.concat(url, "") end +function _ordered_children(node) + local name, child, children = nil, nil, {} + + for name, child in pairs(node.nodes) do + children[#children+1] = { + name = name, + node = child, + order = child.order or 100 + } + end + + table.sort(children, function(a, b) + if a.order == b.order then + return a.name < b.name + else + return a.order < b.order + end + end) + + return children +end + function node_visible(node) if node then return not ( @@ -55,15 +77,10 @@ end function node_childs(node) local rv = { } if node then - local k, v - for k, v in util.spairs(node.nodes, - function(a, b) - return (node.nodes[a].order or 100) - < (node.nodes[b].order or 100) - end) - do - if node_visible(v) then - rv[#rv+1] = k + local _, child + for _, child in ipairs(_ordered_children(node)) do + if node_visible(child.node) then + rv[#rv+1] = child.name end end end @@ -595,11 +612,9 @@ function createtree() local ctx = context local tree = {nodes={}, inreq=true} - local modi = {} ctx.treecache = setmetatable({}, {__mode="v"}) ctx.tree = tree - ctx.modifiers = modi local scope = setmetatable({}, {__index = luci.dispatcher}) @@ -609,28 +624,9 @@ function createtree() v() end - local function modisort(a,b) - return modi[a].order < modi[b].order - end - - for _, v in util.spairs(modi, modisort) do - scope._NAME = v.module - setfenv(v.func, scope) - v.func() - end - return tree end -function modifier(func, order) - context.modifiers[#context.modifiers+1] = { - func = func, - order = order or 0, - module - = getfenv(2)._NAME - } -end - function assign(path, clone, title, order) local obj = node(unpack(path)) obj.nodes = nil @@ -720,24 +716,7 @@ end -- Subdispatchers -- function _find_eligible_node(root, prefix, deep, types, descend) - local _, cur_name, cur_node - local childs = { } - - for cur_name, cur_node in pairs(root.nodes) do - childs[#childs+1] = { - node = cur_node, - name = cur_name, - order = cur_node.order or 100 - } - end - - table.sort(childs, function(a, b) - if a.order == b.order then - return a.name < b.name - else - return a.order < b.order - end - end) + local children = _ordered_children(root) if not root.leaf and deep ~= nil then local sub_path = { unpack(prefix) } @@ -746,10 +725,11 @@ function _find_eligible_node(root, prefix, deep, types, descend) deep = nil end - for _, cur_node in ipairs(childs) do - sub_path[#prefix+1] = cur_node.name + local _, child + for _, child in ipairs(children) do + sub_path[#prefix+1] = child.name - local res_path = _find_eligible_node(cur_node.node, sub_path, + local res_path = _find_eligible_node(child.node, sub_path, deep, types, true) if res_path then diff --git a/modules/luci-base/luasrc/dispatcher.luadoc b/modules/luci-base/luasrc/dispatcher.luadoc index f26256953a..a77f8d8b07 100644 --- a/modules/luci-base/luasrc/dispatcher.luadoc +++ b/modules/luci-base/luasrc/dispatcher.luadoc @@ -82,15 +82,6 @@ Build the index before if it does not exist yet. ]] ---[[ -Register a tree modifier. - -@class function -@name modifier -@param func Modifier function -@param order Modifier order value (optional) -]] - ----[[ Clone a node of the dispatching tree to another position. @class function diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/iface_status.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/iface_status.js new file mode 100644 index 0000000000..88f48d189a --- /dev/null +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/iface_status.js @@ -0,0 +1,42 @@ +requestAnimationFrame(function() { + document.querySelectorAll('[data-iface-status]').forEach(function(container) { + var network = container.getAttribute('data-iface-status'), + icon = container.querySelector('img'), + info = container.querySelector('span'); + + L.poll(5, L.url('admin/network/iface_status', network), null, function(xhr, ifaces) { + var ifc = Array.isArray(ifaces) ? ifaces[0] : null; + if (!ifc) + return; + + L.itemlist(info, [ + _('Device'), ifc.ifname, + _('Uptime'), ifc.is_up ? '%t'.format(ifc.uptime) : null, + _('MAC'), ifc.ifname ? ifc.macaddr : null, + _('RX'), ifc.ifname ? '%.2mB (%d %s)'.format(ifc.rx_bytes, ifc.rx_packets, _('Pkts.')) : null, + _('TX'), ifc.ifname ? '%.2mB (%d %s)'.format(ifc.tx_bytes, ifc.tx_packets, _('Pkts.')) : null, + _('IPv4'), ifc.ipaddrs ? ifc.ipaddrs[0] : null, + _('IPv4'), ifc.ipaddrs ? ifc.ipaddrs[1] : null, + _('IPv4'), ifc.ipaddrs ? ifc.ipaddrs[2] : null, + _('IPv4'), ifc.ipaddrs ? ifc.ipaddrs[3] : null, + _('IPv4'), ifc.ipaddrs ? ifc.ipaddrs[4] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[0] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[1] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[2] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[3] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[4] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[5] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[6] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[7] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[8] : null, + _('IPv6'), ifc.ip6addrs ? ifc.ip6addrs[9] : null, + _('IPv6-PD'), ifc.ip6prefix, + null, ifc.ifname ? null : E('em', _('Interface not present or not connected yet.')) + ]); + + icon.src = L.resource('icons/%s%s.png').format(ifc.type, ifc.is_up ? '' : '_disabled'); + }); + + L.run(); + }); +}); diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js new file mode 100644 index 0000000000..d5bd7b0a6d --- /dev/null +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js @@ -0,0 +1,159 @@ +var poll = null; + +function format_signal(bss) { + var qval = bss.quality || 0, + qmax = bss.quality_max || 100, + scale = 100 / qmax * qval, + range = 'none'; + + if (!bss.bssid || bss.bssid == '00:00:00:00:00:00') + range = 'none'; + else if (scale < 15) + range = '0'; + else if (scale < 35) + range = '0-25'; + else if (scale < 55) + range = '25-50'; + else if (scale < 75) + range = '50-75'; + else + range = '75-100'; + + return E('span', { + class: 'ifacebadge', + title: '%s: %d%s / %s: %d/%d'.format(_('Signal'), bss.signal, _('dB'), _('Quality'), qval, qmax) + }, [ + E('img', { src: L.resource('icons/signal-%s.png').format(range) }), + ' %d%%'.format(scale) + ]); +} + +function format_encryption(bss) { + var enc = bss.encryption || { } + + if (enc.wep === true) + return 'WEP'; + else if (enc.wpa > 0) + return E('abbr', { + title: 'Pairwise: %h / Group: %h'.format( + enc.pair_ciphers.join(', '), + enc.group_ciphers.join(', ')) + }, + '%h - %h'.format( + (enc.wpa === 3) ? _('mixed WPA/WPA2') : (enc.wpa === 2 ? 'WPA2' : 'WPA'), + enc.auth_suites.join(', '))); + else + return E('em', enc.enabled ? _('unknown') : _('open')); +} + +function format_actions(dev, type, bss) { + var enc = bss.encryption || { }, + input = [ + E('input', { type: 'submit', class: 'cbi-button cbi-button-action important', value: _('Join Network') }), + E('input', { type: 'hidden', name: 'token', value: L.env.token }), + E('input', { type: 'hidden', name: 'device', value: dev }), + E('input', { type: 'hidden', name: 'join', value: bss.ssid }), + E('input', { type: 'hidden', name: 'mode', value: bss.mode }), + E('input', { type: 'hidden', name: 'bssid', value: bss.bssid }), + E('input', { type: 'hidden', name: 'channel', value: bss.channel }), + E('input', { type: 'hidden', name: 'clbridge', value: type === 'wl' ? 1 : 0 }), + E('input', { type: 'hidden', name: 'wep', value: enc.wep ? 1 : 0 }) + ]; + + if (enc.wpa) { + input.push(E('input', { type: 'hidden', name: 'wpa_version', value: enc.wpa })); + + enc.auth_suites.forEach(function(s) { + input.push(E('input', { type: 'hidden', name: 'wpa_suites', value: s })); + }); + + enc.group_ciphers.forEach(function(s) { + input.push(E('input', { type: 'hidden', name: 'wpa_group', value: s })); + }); + + enc.pair_ciphers.forEach(function(s) { + input.push(E('input', { type: 'hidden', name: 'wpa_pairwise', value: s })); + }); + } + + return E('form', { + class: 'inline', + method: 'post', + action: L.url('admin/network/wireless_join') + }, input); +} + +function fade(bss, content) { + if (bss.stale) + return E('span', { style: 'opacity:0.5' }, content); + else + return content; +} + +function flush() { + L.stop(poll); + L.halt(); + + scan(); +} + +function scan() { + var tbl = document.querySelector('[data-wifi-scan]'), + dev = tbl.getAttribute('data-wifi-scan'), + type = tbl.getAttribute('data-wifi-type'); + + cbi_update_table(tbl, [], E('em', { class: 'spinning' }, _('Starting wireless scan...'))); + + L.post(L.url('admin/network/wireless_scan_trigger', dev), null, function(s) { + if (s.status !== 204) { + cbi_update_table(tbl, [], E('em', _('Scan request failed'))); + return; + } + + var count = 0; + + poll = L.poll(3, L.url('admin/network/wireless_scan_results', dev), null, function(s, results) { + if (Array.isArray(results)) { + var bss = []; + + results.sort(function(a, b) { + var diff = (b.quality - a.quality) || (a.channel - b.channel); + + if (diff) + return diff; + + if (a.ssid < b.ssid) + return -1; + else if (a.ssid > b.ssid) + return 1; + + if (a.bssid < b.bssid) + return -1; + else if (a.bssid > b.bssid) + return 1; + }).forEach(function(res) { + bss.push([ + fade(res, format_signal(res)), + fade(res, res.ssid ? '%h'.format(res.ssid) : E('em', {}, _('hidden'))), + fade(res, res.channel), + fade(res, res.mode), + fade(res, res.bssid), + fade(res, format_encryption(res)), + format_actions(dev, type, res) + ]); + }); + + cbi_update_table(tbl, bss, E('em', { class: 'spinning' }, _('No scan results available yet...'))); + } + + if (count++ >= 3) { + count = 0; + L.post(L.url('admin/network/wireless_scan_trigger', dev, 1), null, function() {}); + } + }); + + L.run(); + }); +} + +document.addEventListener('DOMContentLoaded', scan); diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js new file mode 100644 index 0000000000..7e14d999bd --- /dev/null +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js @@ -0,0 +1,59 @@ +requestAnimationFrame(function() { + document.querySelectorAll('[data-wifi-status]').forEach(function(container) { + var ifname = container.getAttribute('data-wifi-status'), + small = container.querySelector('small'), + info = container.querySelector('span'); + + L.poll(5, L.url('admin/network/wireless_status', ifname), null, function(xhr, iws) { + var iw = Array.isArray(iws) ? iws[0] : null; + if (!iw) + return; + + var is_assoc = (iw.bssid && iw.bssid != '00:00:00:00:00:00' && iw.channel && !iw.disabled); + var p = iw.quality; + var q = iw.disabled ? -1 : p; + + var icon; + if (q < 0) + icon = L.resource('icons/signal-none.png'); + else if (q == 0) + icon = L.resource('icons/signal-0.png'); + else if (q < 25) + icon = L.resource('icons/signal-0-25.png'); + else if (q < 50) + icon = L.resource('icons/signal-25-50.png'); + else if (q < 75) + icon = L.resource('icons/signal-50-75.png'); + else + icon = L.resource('icons/signal-75-100.png'); + + L.dom.content(small, [ + E('img', { + src: icon, + title: '%s: %d %s / %s: %d %s'.format( + _('Signal'), iw.signal, _('dBm'), + _('Noise'), iw.noise, _('dBm')) + }), + '\u00a0', E('br'), '%d%%\u00a0'.format(p) + ]); + + L.itemlist(info, [ + _('Mode'), iw.mode, + _('SSID'), '%h'.format(iw.ssid || '?'), + _('BSSID'), is_assoc ? iw.bssid : null, + _('Encryption'), is_assoc ? iw.encryption || _('None') : null, + _('Channel'), is_assoc ? '%d (%.3f %s)'.format(iw.channel, iw.frequency || 0, _('GHz')) : null, + _('Tx-Power'), is_assoc ? '%d %s'.format(iw.txpower, _('dBm')) : null, + _('Signal'), is_assoc ? '%d %s'.format(iw.signal, _('dBm')) : null, + _('Noise'), is_assoc ? '%d %s'.format(iw.noise, _('dBm')) : null, + _('Bitrate'), is_assoc ? '%.1f %s'.format(iw.bitrate || 0, _('Mbit/s')) : null, + _('Country'), is_assoc ? iw.country : null + ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]); + + if (!is_assoc) + L.dom.append(info, E('em', iw.disabled ? _('Wireless is disabled') : _('Wireless is not associated'))); + }); + + L.run(); + }); +}); diff --git a/modules/luci-mod-network/luasrc/controller/admin/network.lua b/modules/luci-mod-network/luasrc/controller/admin/network.lua index a200f79b51..1da5eac464 100644 --- a/modules/luci-mod-network/luasrc/controller/admin/network.lua +++ b/modules/luci-mod-network/luasrc/controller/admin/network.lua @@ -321,7 +321,7 @@ function wifi_scan_trigger(radio, update) return end - luci.http.status(200, "Scan scheduled") + luci.http.status(204, "Scan scheduled") if nixio.fork() == 0 then io.stderr:close() diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua index 8ed39df486..9ab282c3ab 100644 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua +++ b/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua @@ -17,7 +17,9 @@ local acct_port, acct_secret, acct_server, anonymous_identity, ant1, ant2, privkeypwd2, r0_key_lifetime, r0kh, r1_key_holder, r1kh, reassociation_deadline, retry_timeout, ssid, st, tp, wepkey, wepslot, wmm, wpakey, wps, disassoc_low_ack, short_preamble, beacon_int, dtim_period, - wparekey, inactivitypool, maxinactivity, listeninterval + wparekey, inactivitypool, maxinactivity, listeninterval, + dae_client, dae_port, dae_port + arg[1] = arg[1] or "" @@ -755,6 +757,30 @@ acct_secret:depends({mode="ap-wds", encryption="wpa2"}) acct_secret.rmempty = true acct_secret.password = true +dae_client = s:taboption("encryption", Value, "dae_client", translate("DAE-Client")) +dae_client:depends({mode="ap", encryption="wpa"}) +dae_client:depends({mode="ap", encryption="wpa2"}) +dae_client:depends({mode="ap-wds", encryption="wpa"}) +dae_client:depends({mode="ap-wds", encryption="wpa2"}) +dae_client.rmempty = true +dae_client.datatype = "host(0)" + +dae_port = s:taboption("encryption", Value, "dae_port", translate("DAE-Port"), translatef("Default %d", 3799)) +dae_port:depends({mode="ap", encryption="wpa"}) +dae_port:depends({mode="ap", encryption="wpa2"}) +dae_port:depends({mode="ap-wds", encryption="wpa"}) +dae_port:depends({mode="ap-wds", encryption="wpa2"}) +dae_port.rmempty = true +dae_port.datatype = "port" + +dae_secret = s:taboption("encryption", Value, "dae_secret", translate("DAE-Secret")) +dae_secret:depends({mode="ap", encryption="wpa"}) +dae_secret:depends({mode="ap", encryption="wpa2"}) +dae_secret:depends({mode="ap-wds", encryption="wpa"}) +dae_secret:depends({mode="ap-wds", encryption="wpa2"}) +dae_secret.rmempty = true +dae_secret.password = true + wpakey = s:taboption("encryption", Value, "_wpa_key", translate("Key")) wpakey:depends("encryption", "psk") wpakey:depends("encryption", "psk2") @@ -872,12 +898,14 @@ if hwtype == "mac80211" or hwtype == "prism2" then ft_psk_generate_local = s:taboption("encryption", Flag, "ft_psk_generate_local", translate("Generate PMK locally"), - translate("When using a PSK, the PMK can be generated locally without inter AP communications")) + translate("When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.")) ft_psk_generate_local:depends({ieee80211r="1"}) + ft_psk_generate_local.default = ft_psk_generate_local.enabled + ft_psk_generate_local.rmempty = false r0_key_lifetime = s:taboption("encryption", Value, "r0_key_lifetime", translate("R0 Key Lifetime"), translate("minutes")) - r0_key_lifetime:depends({ieee80211r="1", ft_psk_generate_local=""}) + r0_key_lifetime:depends({ieee80211r="1"}) r0_key_lifetime.placeholder = "10000" r0_key_lifetime.datatype = "uinteger" r0_key_lifetime.rmempty = true @@ -885,13 +913,13 @@ if hwtype == "mac80211" or hwtype == "prism2" then r1_key_holder = s:taboption("encryption", Value, "r1_key_holder", translate("R1 Key Holder"), translate("6-octet identifier as a hex string - no colons")) - r1_key_holder:depends({ieee80211r="1", ft_psk_generate_local=""}) + r1_key_holder:depends({ieee80211r="1"}) r1_key_holder.placeholder = "00004f577274" r1_key_holder.datatype = "and(hexstring,rangelength(12,12))" r1_key_holder.rmempty = true pmk_r1_push = s:taboption("encryption", Flag, "pmk_r1_push", translate("PMK R1 Push")) - pmk_r1_push:depends({ieee80211r="1", ft_psk_generate_local=""}) + pmk_r1_push:depends({ieee80211r="1"}) pmk_r1_push.placeholder = "0" pmk_r1_push.rmempty = true @@ -901,7 +929,7 @@ if hwtype == "mac80211" or hwtype == "prism2" then "<br />This list is used to map R0KH-ID (NAS Identifier) to a destination " .. "MAC address when requesting PMK-R1 key from the R0KH that the STA " .. "used during the Initial Mobility Domain Association.")) - r0kh:depends({ieee80211r="1", ft_psk_generate_local=""}) + r0kh:depends({ieee80211r="1"}) r0kh.rmempty = true r1kh = s:taboption("encryption", DynamicList, "r1kh", translate("External R1 Key Holder List"), @@ -910,7 +938,7 @@ if hwtype == "mac80211" or hwtype == "prism2" then "<br />This list is used to map R1KH-ID to a destination MAC address " .. "when sending PMK-R1 key from the R0KH. This is also the " .. "list of authorized R1KHs in the MD that can request PMK-R1 keys.")) - r1kh:depends({ieee80211r="1", ft_psk_generate_local=""}) + r1kh:depends({ieee80211r="1"}) r1kh.rmempty = true -- End of 802.11r options diff --git a/modules/luci-mod-network/luasrc/view/admin_network/iface_status.htm b/modules/luci-mod-network/luasrc/view/admin_network/iface_status.htm index 34be35dd20..a75b2755cd 100644 --- a/modules/luci-mod-network/luasrc/view/admin_network/iface_status.htm +++ b/modules/luci-mod-network/luasrc/view/admin_network/iface_status.htm @@ -1,66 +1,12 @@ <%+cbi/valueheader%> -<script type="text/javascript">//<![CDATA[ - XHR.poll(5, '<%=url('admin/network/iface_status', self.network)%>', null, - function(x, ifc) - { - if (ifc && (ifc = ifc[0])) - { - var s = document.getElementById('<%=self.option%>-ifc-status'), - img = s.querySelector('img'), - info = s.querySelector('span'), - html = '<strong><%:Device%>:</strong> %h<br />'.format(ifc.ifname); - - if (ifc.ifname) - { - if (ifc.is_up) - html += String.format('<strong><%:Uptime%>:</strong> %t<br />', ifc.uptime); - - if (ifc.macaddr) - html += String.format('<strong><%:MAC%>:</strong> %s<br />', ifc.macaddr); - - html += String.format( - '<strong><%:RX%></strong>: %.2mB (%d <%:Pkts.%>)<br />' + - '<strong><%:TX%></strong>: %.2mB (%d <%:Pkts.%>)<br />', - ifc.rx_bytes, ifc.rx_packets, - ifc.tx_bytes, ifc.tx_packets - ); - - if (ifc.ipaddrs && ifc.ipaddrs.length) - for (var i = 0; i < ifc.ipaddrs.length; i++) - html += String.format( - '<strong><%:IPv4%>:</strong> %s<br />', - ifc.ipaddrs[i] - ); - - if (ifc.ip6addrs && ifc.ip6addrs.length) - for (var i = 0; i < ifc.ip6addrs.length; i++) - html += String.format( - '<strong><%:IPv6%>:</strong> %s<br />', - ifc.ip6addrs[i] - ); - - if (ifc.ip6prefix) - html += String.format('<strong><%:IPv6-PD%>:</strong> %s<br />', ifc.ip6prefix); - - info.innerHTML = html; - } - else - { - info.innerHTML = '<em><%:Interface not present or not connected yet.%></em>'; - } - - img.src = '<%=resource%>/icons/%s%s.png'.format(ifc.type, ifc.is_up ? '' : '_disabled'); - } - } - ); -//]]></script> - -<span class="ifacebadge large" id="<%=self.option%>-ifc-status"> +<span class="ifacebadge large"<%=attr("data-iface-status", self.network)%>> <img src="<%=resource%>/icons/ethernet_disabled.png" /> <span> - <em><%:Collecting data...%></em> + <em class="spinning"><%:Collecting data...%></em> </span> </span> +<script type="text/javascript" src="<%=resource%>/view/network/iface_status.js"></script> + <%+cbi/valuefooter%> diff --git a/modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm b/modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm index 987123642f..5a61ba099c 100644 --- a/modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm +++ b/modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm @@ -19,185 +19,18 @@ <%+header%> -<script type="text/javascript">//<![CDATA[ - var xhr = new XHR(), - poll = null; - - function format_signal(bss) { - var qval = bss.quality || 0, - qmax = bss.quality_max || 100, - scale = 100 / qmax * qval, - range = 'none'; - - if (!bss.bssid || bss.bssid == '00:00:00:00:00:00') - range = 'none'; - else if (scale < 15) - range = '0'; - else if (scale < 35) - range = '0-25'; - else if (scale < 55) - range = '25-50'; - else if (scale < 75) - range = '50-75'; - else - range = '75-100'; - - return E('span', { - class: 'ifacebadge', - title: '<%:Signal%>: %d<%:dB%> / <%:Quality%>: %d/%d'.format(bss.signal, qval, qmax) - }, [ - E('img', { src: '<%=resource%>/icons/signal-%s.png'.format(range) }), - ' %d%%'.format(scale) - ]); - } - - function format_encryption(bss) { - var enc = bss.encryption || { } - - if (enc.wep === true) - return 'WEP'; - else if (enc.wpa > 0) - return E('abbr', { - title: 'Pairwise: %h / Group: %h'.format( - enc.pair_ciphers.join(', '), - enc.group_ciphers.join(', ')) - }, - '%h - %h'.format( - (enc.wpa === 3) ? '<%:mixed WPA/WPA2%>' : (enc.wpa === 2 ? 'WPA2' : 'WPA'), - enc.auth_suites.join(', '))); - else if (enc.enabled) - return '<em><%:unknown%></em>'; - else - return '<em><%:open%></em>'; - } - - function format_actions(bss) { - var enc = bss.encryption || { }, - input = [ - E('input', { type: 'submit', class: 'cbi-button cbi-button-action important', value: '<%:Join Network%>' }), - E('input', { type: 'hidden', name: 'token', value: '<%=token%>' }), - E('input', { type: 'hidden', name: 'device', value: '<%=dev%>' }), - E('input', { type: 'hidden', name: 'join', value: bss.ssid }), - E('input', { type: 'hidden', name: 'mode', value: bss.mode }), - E('input', { type: 'hidden', name: 'bssid', value: bss.bssid }), - E('input', { type: 'hidden', name: 'channel', value: bss.channel }), - E('input', { type: 'hidden', name: 'clbridge', value: <%=iw.type == "wl" and 1 or 0%> }), - E('input', { type: 'hidden', name: 'wep', value: enc.wep ? 1 : 0 }) - ]; - - if (enc.wpa) { - input.push(E('input', { type: 'hidden', name: 'wpa_version', value: enc.wpa })); - - enc.auth_suites.forEach(function(s) { - input.push(E('input', { type: 'hidden', name: 'wpa_suites', value: s })); - }); - - enc.group_ciphers.forEach(function(s) { - input.push(E('input', { type: 'hidden', name: 'wpa_group', value: s })); - }); - - enc.pair_ciphers.forEach(function(s) { - input.push(E('input', { type: 'hidden', name: 'wpa_pairwise', value: s })); - }); - } - - return E('form', { - class: 'inline', - method: 'post', - action: '<%=url("admin/network/wireless_join")%>' - }, input); - } - - function fade(bss, content) { - if (bss.stale) - return E('span', { style: 'opacity:0.5' }, content); - else - return content; - } - - function flush() { - XHR.stop(poll); - XHR.halt(); - - scan(); - } - - function scan() { - var tbl = document.getElementById('scan_results'); - - cbi_update_table(tbl, [], '<em><img src="<%=resource%>/icons/loading.gif" class="middle" /> <%:Starting wireless scan...%></em>'); - - xhr.post('<%=url("admin/network/wireless_scan_trigger", dev)%>', { token: '<%=token%>' }, - function(s) { - if (s.status !== 200) { - cbi_update_table(tbl, [], '<em><%:Scan request failed%></em>'); - return; - } - - var count = 0; - - poll = XHR.poll(3, '<%=url("admin/network/wireless_scan_results", dev)%>', null, - function(s, results) { - if (Array.isArray(results)) { - var bss = []; - - results.sort(function(a, b) { - var diff = (b.quality - a.quality) || (a.channel - b.channel); - - if (diff) - return diff; - - if (a.ssid < b.ssid) - return -1; - else if (a.ssid > b.ssid) - return 1; - - if (a.bssid < b.bssid) - return -1; - else if (a.bssid > b.bssid) - return 1; - }).forEach(function(res) { - bss.push([ - fade(res, format_signal(res)), - fade(res, res.ssid ? '%h'.format(res.ssid) : E('em', {}, '<%:hidden%>')), - fade(res, res.channel), - fade(res, res.mode), - fade(res, res.bssid), - fade(res, format_encryption(res)), - format_actions(res) - ]); - }); - - cbi_update_table(tbl, bss, '<em><img src="<%=resource%>/icons/loading.gif" class="middle" /> <%:No scan results available yet...%>'); - } - - if (count++ >= 3) { - count = 0; - xhr.post('<%=url("admin/network/wireless_scan_trigger", dev, "1")%>', - { token: '<%=token%>' }, function() { }); - } - }); - - XHR.run(); - }); - } - - document.addEventListener('DOMContentLoaded', scan); - -//]]></script> - <h2 name="content"><%:Join Network: Wireless Scan%></h2> <div class="cbi-map"> <div class="cbi-section"> - <div class="table" id="scan_results"> + <div class="table"<%=attr("data-wifi-scan", dev) .. attr("data-wifi-type", iw.type)%>> <div class="tr table-titles"> - <div class="th col-1 middle center"><%:Signal%></div> - <div class="th col-5 middle left"><%:SSID%></div> - <div class="th col-2 middle center"><%:Channel%></div> - <div class="th col-2 middle left"><%:Mode%></div> - <div class="th col-3 middle left"><%:BSSID%></div> - <div class="th col-2 middle left"><%:Encryption%></div> + <div class="th col-2 middle center"><%:Signal%></div> + <div class="th col-4 middle left"><%:SSID%></div> + <div class="th col-2 middle center hide-xs"><%:Channel%></div> + <div class="th col-2 middle left hide-xs"><%:Mode%></div> + <div class="th col-3 middle left hide-xs"><%:BSSID%></div> + <div class="th col-3 middle left"><%:Encryption%></div> <div class="th cbi-section-actions"> </div> </div> @@ -221,4 +54,6 @@ </form> </div> +<script type="text/javascript" src="<%=resource%>/view/network/wifi_join.js"></script> + <%+footer%> diff --git a/modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm b/modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm index bfad3d0804..93ae2f51fb 100644 --- a/modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm +++ b/modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm @@ -1,77 +1,14 @@ <%+cbi/valueheader%> -<script type="text/javascript">//<![CDATA[ - XHR.poll(5, '<%=url('admin/network/wireless_status', self.ifname)%>', null, - function(x, iw) - { - if (iw && (iw = iw[0])) - { - var is_assoc = (iw.bssid && iw.bssid != '00:00:00:00:00:00' && iw.channel && !iw.disabled); - var p = iw.quality; - var q = iw.disabled ? -1 : p; - - var icon; - if (q < 0) - icon = "<%=resource%>/icons/signal-none.png"; - else if (q == 0) - icon = "<%=resource%>/icons/signal-0.png"; - else if (q < 25) - icon = "<%=resource%>/icons/signal-0-25.png"; - else if (q < 50) - icon = "<%=resource%>/icons/signal-25-50.png"; - else if (q < 75) - icon = "<%=resource%>/icons/signal-50-75.png"; - else - icon = "<%=resource%>/icons/signal-75-100.png"; - - var s = document.getElementById('<%=self.option%>-iw-status'), - small = s.querySelector('small'), - info = s.querySelector('span'); - - small.innerHTML = info.innerHTML = String.format( - '<img src="%s" title="<%:Signal%>: %d <%:dBm%> / <%:Noise%>: %d <%:dBm%>" /> <br />%d%% ', - icon, iw.signal, iw.noise, p - ); - - if (is_assoc) - info.innerHTML = String.format( - '<strong><%:Mode%>:</strong> %s | ' + - '<strong><%:SSID%>:</strong> %h<br />' + - '<strong><%:BSSID%>:</strong> %s<br />' + - '<strong><%:Encryption%>:</strong> %s<br />' + - '<strong><%:Channel%>:</strong> %d (%.3f <%:GHz%>)<br />' + - '<strong><%:Tx-Power%>:</strong> %d <%:dBm%><br />' + - '<strong><%:Signal%>:</strong> %d <%:dBm%> | ' + - '<strong><%:Noise%>:</strong> %d <%:dBm%><br />' + - '<strong><%:Bitrate%>:</strong> %.1f <%:Mbit/s%> | ' + - '<strong><%:Country%>:</strong> %s', - iw.mode, iw.ssid, iw.bssid, - iw.encryption ? iw.encryption : '<%:None%>', - iw.channel, iw.frequency ? iw.frequency : 0, - iw.txpower, iw.signal, iw.noise, - iw.bitrate ? iw.bitrate : 0, iw.country - ); - else - info.innerHTML = String.format( - '<strong><%:SSID%>:</strong> %h | ' + - '<strong><%:Mode%>:</strong> %s<br />' + - '<em>%s</em>', - iw.ssid || '?', iw.mode, - iw.disabled ? '<em><%:Wireless is disabled%></em>' - : '<em><%:Wireless is not associated%></em>' - ); - } - } - ); -//]]></script> - -<span class="ifacebadge large" id="<%=self.option%>-iw-status"> +<span class="ifacebadge large"<%=attr("data-wifi-status", self.ifname)%>> <small> <img src="<%=resource%>/icons/signal-none.png" title="<%:Not associated%>" />  </small> <span> - <em><%:Collecting data...%></em> + <em class="spinning"><%:Collecting data...%></em> </span> </span> +<script type="text/javascript" src="<%=resource%>/view/network/wifi_status.js"></script> + <%+cbi/valuefooter%> diff --git a/modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm b/modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm index 77efa11a0f..e0917995e4 100644 --- a/modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm +++ b/modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm @@ -4,6 +4,19 @@ .cbi-dynlist { max-width: 100%; } + + .cbi-dynlist .item > small { + display: block; + direction: rtl; + overflow: hidden; + text-align: left; + } + + .cbi-dynlist .item > small > code { + direction: ltr; + white-space: nowrap; + unicode-bidi: bidi-override; + } </style> <div class="cbi-map"> diff --git a/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css b/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css index 2322a73857..98f6022ca0 100644 --- a/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css +++ b/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css @@ -174,6 +174,10 @@ a:hover { float: left; } +.nowrap { + white-space: nowrap; +} + /* Typography.less * Headings, body text, lists, code, and more for a versatile and durable typography system * ---------------------------------------------------------------------------------------- */ @@ -500,8 +504,12 @@ select, display: flex; } -.cbi-dynlist > .add-item > input { +.cbi-dynlist > .add-item > input, +.cbi-dynlist > .add-item > button { flex: 1 1 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } select { @@ -1012,44 +1020,48 @@ header .dropdown-menu a.hover, } .tabs, .cbi-tabmenu { - margin: 0 0 18px; - padding: 0; + margin: 0 -5px 18px; + padding: 0 2px; list-style: none; - zoom: 1; -} - -.tabs:before, -.cbi-tabmenu:before, -.tabs:after, -.cbi-tabmenu:after { - display: table; - content: ""; - zoom: 1; -} - -.tabs:after, .cbi-tabmenu:after { - clear: both; + display: flex; + flex-wrap: wrap; + background: linear-gradient(#fff 28px, #ddd 28px); + background-size: 1px 29px; + background-position: left bottom; } .tabs > li, .cbi-tabmenu > li { - float: left; + flex: 0 1 auto; + display: flex; + align-items: center; + height: 25px; + max-width: 48%; + margin: 4px 2px 0 2px; + background: #fff; + border: 1px solid #ddd; + border-bottom: none; + border-radius: 4px 4px 0 0; + color: #0069d6; } .tabs > li > a, .cbi-tabmenu > li > a { - display: block; + padding: 4px 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: inherit; + text-decoration: none; + border-radius: 4px 4px 0 0; + line-height: 25px; } -.tabs, -.cbi-tabmenu { - border-color: #ddd; - border-style: solid; - border-width: 0 0 1px; +.tabs > li:not(.active):hover, .cbi-tabmenu > .cbi-tab-disabled:hover { + background: linear-gradient(#fff 90%, #ddd 100%); } -.tabs > li, -.cbi-tabmenu > li { - position: relative; - margin-bottom: -1px; +.tabs > li:not(.active), .cbi-tabmenu > .cbi-tab-disabled { + color: #999; + background: linear-gradient(#eee 90%, #ddd 100%); } .cbi-tabmenu.map { @@ -1065,53 +1077,23 @@ header .dropdown-menu a.hover, display: none; } -.tabs > li > a, -.cbi-tabmenu > li > a { - padding: 0 15px; - margin-right: 2px; - line-height: 34px; - border: 1px solid transparent; - border-radius: 4px 4px 0 0; -} - -.tabs > li > a:hover, -.cbi-tabmenu > li > a:hover { - text-decoration: none; - background-color: #eee; - border-color: #eee #eee #ddd; -} - -.tabs .active > a, .tabs .active > a:hover, -.cbi-tabmenu .active > a, .cbi-tabmenu .active > a:hover, -.cbi-tab > a:link, .cbi-tab > a:hover { - color: #808080; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent; - cursor: default; -} - -.tabs .menu-dropdown, .tabs .dropdown-menu, -.cbi-tabmenu .menu-dropdown, .cbi-tabmenu .dropdown-menu { +.tabs .menu-dropdown, .tabs .dropdown-menu { top: 35px; border-width: 1px; border-radius: 0 6px 6px 6px; } -.tabs a.menu:after, .tabs .dropdown-toggle:after, -.cbi-tabmenu a.menu:after, .cbi-tabmenu .dropdown-toggle:after { +.tabs a.menu:after, .tabs .dropdown-toggle:after { border-top-color: #999; margin-top: 15px; margin-left: 5px; } -.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle, -.cbi-tabmenu li.open.menu .menu, .cbi-tabmenu .open.dropdown .dropdown-toggle { +.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle { border-color: #999; } -.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after, -.cbi-tabmenu li.open a.menu:after, .cbi-tabmenu .dropdown.open .dropdown-toggle:after { +.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after { border-top-color: #555; } @@ -1167,6 +1149,7 @@ footer { -webkit-overflow-scrolling: touch; transition: opacity .125s ease-in; opacity: 0; + visibility: hidden; } .modal { @@ -1206,6 +1189,7 @@ body.modal-overlay-active #modal_overlay { left: 0; right: 0; opacity: 1; + visibility: visible; } .btn.danger, diff --git a/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/mobile.css b/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/mobile.css index b74f209045..062d274b75 100644 --- a/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/mobile.css +++ b/themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/mobile.css @@ -6,11 +6,367 @@ header h3 a, header .brand { #maincontent.container { margin-top: 30px; } + + .tabs, .cbi-tabmenu { + background: linear-gradient(#fff 20%, #ddd 100%); + background-size: 1px 34px; + margin-bottom: 10px; + } + + .tabs > li, .cbi-tabmenu > li { + height: 30px; + } + + .tabs > li > a, .cbi-tabmenu > li > a { + padding: 0 8px; + line-height: 30px; + } + + .table { + display: flex; + flex-direction: column; + width: 100%; + } + + .tr { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-end; + border-top: 1px solid #ddd; + padding: 5px 0; + margin: 0 -3px; + } + + .table .th, + .table .td, + .table .tr::before { + flex: 2 2 33%; + align-self: flex-start; + overflow: hidden; + text-overflow: ellipsis; + word-wrap: break-word; + display: inline-block; + border-top: none; + padding: 3px; + box-sizing: border-box; + } + + .table .td.cbi-dropdown-open { + overflow: visible; + } + + .col-1 { flex: 1 1 30px !important; -webkit-flex: 1 1 30px !important; } + .col-2 { flex: 2 2 60px !important; -webkit-flex: 2 2 60px !important; } + .col-3 { flex: 3 3 90px !important; -webkit-flex: 3 3 90px !important; } + .col-4 { flex: 4 4 120px !important; -webkit-flex: 4 4 120px !important; } + .col-5 { flex: 5 5 150px !important; -webkit-flex: 5 5 150px !important; } + .col-6 { flex: 6 6 180px !important; -webkit-flex: 6 6 180px !important; } + .col-7 { flex: 7 7 210px !important; -webkit-flex: 7 7 210px !important; } + .col-8 { flex: 8 8 240px !important; -webkit-flex: 8 8 240px !important; } + .col-9 { flex: 9 9 270px !important; -webkit-flex: 9 9 270px !important; } + .col-10 { flex: 10 10 300px !important; -webkit-flex: 10 10 300px !important; } + + .td select { + word-wrap: normal; + } + + .td[data-type="button"], + .td[data-type="fvalue"] { + flex: 1 1 17%; + text-align: left; + } + + .td.cbi-value-field { + align-self: flex-start; + } + + .td.cbi-value-field .cbi-button { + width: 100%; + } + + .table.cbi-section-table { + border: none; + background: none; + margin: 0; + } + + .tr.table-titles, + .cbi-section-table-titles, + .cbi-section-table-descr { + display: none; + } + + .cbi-section-table-row { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0 0 .5em 0; + } + + .cbi-section-table + .cbi-section-create { + padding-top: 0; + } + + .tr[data-title]::before { + display: block; + flex: 1 1 100%; + background: #f5f5f5 !important; + font-size: 16px; + border-bottom: 1px solid #ddd; + } + + .td[data-title]::before, + .td[data-description]::after { + display: block; + } + + .td[data-title] ~ .td.cbi-section-actions { + align-self: flex-start; + } + + .td[data-title] ~ .td.cbi-section-actions::before { + display: block; + content: "\a0"; + } + + .td.cbi-section-actions { + overflow: initial; + max-width: 100%; + padding: 3px 2px; + } + + .hide-sm, + .hide-xs { + display: none !important; + } + + .td.cbi-value-field { + flex-basis: 100%; + } + + .td.cbi-value-field[data-type="dvalue"] { + flex-basis: 50%; + } + + .td.cbi-value-field[data-type="button"], + .td.cbi-value-field[data-type="fvalue"] { + flex-basis: 25%; + text-align: left; + } + + .cbi-section-table .tr:hover .td, + .cbi-section-table .tr:hover .th, + .cbi-section-table .tr:hover::before { + background-color: transparent; + } + + .cbi-value { + padding-bottom: .5em; + border-bottom: 1px solid #ddd; + margin-bottom: .5em; + } + + .cbi-value label.cbi-value-title { + float: none; + font-weight: bold; + } + + .cbi-value-field, .cbi-dropdown { + width: 100%; + margin: 0; + } + + input, textarea, select { + font-size: 16px !important; + line-height: 28px; + } + + select, input[type="text"], input[type="password"] { + width: 100%; + height: 30px; + } + + input.cbi-input-password { + width: calc(100% - 25px); + } + + [data-dynlist] { + display: block; + } + + [data-dynlist] > .add-item > input { + width: calc(100% - 21px); + } + + [data-dynlist] > .add-item > .cbi-button { + margin-right: -1px; + } + + input[type="text"] + .cbi-button, + input[type="password"] + .cbi-button, + select + .cbi-button { + font-size: 14px !important; + line-height: 28px; + height: 30px; + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; + } + + .cbi-value-field input[type="checkbox"], + .cbi-value-field input[type="radio"] { + margin: 0; + } + + .btn, .cbi-button { + font-size: 14px !important; + padding: 4px 8px; + } + + .actions, + .cbi-page-actions { + border-top: none; + margin-top: -.5em; + padding: 8px; + } + + [data-page="admin-status-overview"] .cbi-section:nth-of-type(1) .td:first-child, + [data-page="admin-status-overview"] .cbi-section:nth-of-type(2) .td:first-child { + flex-grow: 1; + } + + header .pull-right .label { + white-space: normal; + display: inline-block; + text-align: center; + line-height: 12px; + margin: 1px 0; + } + + header > .fill { + padding: 1px; + } + + header > .fill > .container { + display: flex; + flex-direction: row; + } + + header .nav { + flex: 3 3 80%; + margin: 2px 5px 2px 0; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + } + + header .nav a { + padding: 2px 6px; + } + + header .pull-right { + flex: 1 1 20%; + display: flex; + flex-direction: column; + padding: 0; + justify-content: space-around; + } + + .menu-dropdown, .dropdown-menu { + top: 23px; + } + + body { + padding-top: 30px; + } + + .cbi-optionals, + .cbi-section-create { + padding: 0 0 14px 0; + } + + #cbi-network-switch_vlan .th, + #cbi-network-switch_vlan .td { + flex-basis: 12%; + } + + #cbi-network-switch_vlan .td.cbi-section-actions { + flex-basis: 100%; + } + + #cbi-network-switch_vlan .td.cbi-section-actions::before { + display: none; + } + + #cbi-network-switch_vlan .td.cbi-section-actions > * { + width: auto; + display: block; + } + + #wifi_assoclist_table .td, + [data-page="admin-status-processes"] .td { + flex-basis: 50% !important; + } + + [data-page="admin-status-processes"] .td[data-type="button"] { + flex-basis: 33% !important; + } + + [data-page="admin-status-processes"] .td[data-name="PID"], + [data-page="admin-status-processes"] .td[data-name="USER"] { + flex-basis: 25% !important; + } + + [data-page="admin-system-fstab"] .td[data-type="button"]::before, + [data-page="admin-system-startup"] .td[data-type="button"]::before, + [data-page="admin-status-processes"] .td[data-type="button"]::before { + display: none; + } } -@media screen and (max-device-width: 360px) { +@media screen and (max-device-width: 375px) { #maincontent.container { - margin-top: 60px; + margin-top: 55px; + } + + .cbi-page-actions { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin: 0 -1px; + padding: 0; + } + + .cbi-page-actions .cbi-button { + flex: 1 1 calc(50% - 2px); + margin: 1px !important; + overflow: hidden; + text-overflow: ellipsis; + } + + .cbi-page-actions .cbi-button-primary, + .cbi-page-actions .cbi-button-apply { + flex-basis: calc(100% - -2px); + } + + .cbi-section-actions .cbi-button { + overflow: hidden; + text-overflow: ellipsis; + } + + body[data-page="admin-network-wireless"] .td.col-2 { + max-width: 50px; + } + + body[data-page="admin-network-wireless"] .td.col-2 > .ifacebadge { + display: flex; + align-items: center; + flex-direction: column; + } + + body[data-page="admin-network-network"] .td.col-3 { + min-width: 250px; } } @@ -18,4 +374,29 @@ header h3 a, header .brand { #maincontent.container { margin-top: 230px; } -}
\ No newline at end of file +} + +@media screen and (max-width: 375px) { + .td .ifacebox { + width: 100%; + margin: 0 !important; + flex-direction: row; + } + + .td .ifacebox .ifacebox-head { + min-width: 25%; + justify-content: space-around; + } + + .td .ifacebox .ifacebox-head, + .td .ifacebox .ifacebox-body { + display: flex; + border-bottom: none; + align-items: center; + } + + .td .ifacebox .ifacebox-head > *, + .ifacebox .ifacebox-body > * { + margin: .125em; + } +} diff --git a/themes/luci-theme-material/htdocs/luci-static/material/favicon.ico b/themes/luci-theme-material/htdocs/luci-static/material/favicon.ico Binary files differindex b407d18455..72c311555f 100755..100644 --- a/themes/luci-theme-material/htdocs/luci-static/material/favicon.ico +++ b/themes/luci-theme-material/htdocs/luci-static/material/favicon.ico diff --git a/themes/luci-theme-material/htdocs/luci-static/material/logo.png b/themes/luci-theme-material/htdocs/luci-static/material/logo.png Binary files differdeleted file mode 100755 index 459148c6b6..0000000000 --- a/themes/luci-theme-material/htdocs/luci-static/material/logo.png +++ /dev/null diff --git a/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css b/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css index f6ea9645ff..f8133833e8 100644 --- a/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css +++ b/themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css @@ -211,6 +211,7 @@ hr { -webkit-overflow-scrolling: touch; transition: opacity .125s ease-in; opacity: 0; + visibility: hidden; } .modal { @@ -250,6 +251,7 @@ body.modal-overlay-active #modal_overlay { left: 0; right: 0; opacity: 1; + visibility: visible; } .warning { |