Merge pull request #2329 from TDT-AG/pr/20181123-luci-theme-material
authorJo-Philipp Wich <jo@mein.io>
Tue, 27 Nov 2018 22:46:08 +0000 (23:46 +0100)
committerGitHub <noreply@github.com>
Tue, 27 Nov 2018 22:46:08 +0000 (23:46 +0100)
luci-theme-material: add missing css cbi-section-error definitions

24 files changed:
applications/luci-app-openvpn/luasrc/model/cbi/openvpn-advanced.lua
applications/luci-app-openvpn/luasrc/model/cbi/openvpn-basic.lua
applications/luci-app-openvpn/luasrc/model/cbi/openvpn-file.lua
applications/luci-app-openvpn/luasrc/model/cbi/openvpn.lua
applications/luci-app-openvpn/luasrc/view/openvpn/cbi-select-input-add.htm
applications/luci-app-openvpn/luasrc/view/openvpn/ovpn_css.htm
applications/luci-app-openvpn/luasrc/view/openvpn/pageswitch.htm
modules/luci-base/htdocs/luci-static/resources/luci.js
modules/luci-base/luasrc/dispatcher.lua
modules/luci-base/luasrc/dispatcher.luadoc
modules/luci-mod-network/htdocs/luci-static/resources/view/network/iface_status.js [new file with mode: 0644]
modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js [new file with mode: 0644]
modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js [new file with mode: 0644]
modules/luci-mod-network/luasrc/controller/admin/network.lua
modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua
modules/luci-mod-network/luasrc/view/admin_network/iface_status.htm
modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm
modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm
modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm
themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css
themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/mobile.css
themes/luci-theme-material/htdocs/luci-static/material/favicon.ico [changed mode: 0755->0644]
themes/luci-theme-material/htdocs/luci-static/material/logo.png [deleted file]
themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css

index 9a37ba80221185be4919e7c3e8afd739b29d3498..25d1481f8ad5664594432e97572a9295fb01d386 100644 (file)
@@ -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") },
        } }
 }
 
index 3be274dc8b0bd6aba914b046687613ba1503a7f3..3e9137baeb027cd28646bb210667b5cada2bd4ec 100644 (file)
@@ -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") },
 }
 
 
index 6878275d78b0512e46ce19c3ec9363b1696237e3..9d50601b1f9df53f1beb3d81acde7158bc088999 100644 (file)
@@ -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
 
index ad607ae6c2183341f7b07f8daf9ae03b917d69fb..41266d860e523b3c05921287e74f42c17bd5aa54 100644 (file)
@@ -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
 
index 09da2eb22df799f9c423e673af0c7495c3decf8a..e75bfda900bd3fcfb0fa9f42167dd34af00fcbff 100644 (file)
@@ -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];
 
        <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 %>
                                        <% 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>
index c7062b8d7a9a13ed3f2e25581f5aa20c83d72c06..55c0a543fc09e8ac4125404cf78613c8820c1554 100644 (file)
                border: 0px;
                text-align: left;
        }
-       .td
-       {
-               text-align: left;
-               border-top: 0px;
-               margin: 5px;
-       }
        .vpn-output
        {
                box-shadow: none;
index 17beef0d39cc80b153e392776614e4135a3e9787..c1fe05215a1c3462bc22918d9a49642bf394331f 100644 (file)
                <a href="<%=url('admin/services/openvpn')%>"><%:Overview%></a> &#187;
                <%=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%> &#187;</a><p/>
-               <a href="<%=url('admin/services/openvpn/advanced', self.instance, "Service")%>"><%:Switch to advanced configuration%> &#187;</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%> &#187;</a><p/>
-               <a href="<%=url('admin/services/openvpn/file', self.instance)%>"><%:Switch to file based configuration%> &#187;</a>
                <hr />
        <% elseif self.mode == "advanced" then %>
                <a href="<%=url('admin/services/openvpn/basic', self.instance)%>"><%:Switch to basic configuration%> &#187;</a><p/>
-               <a href="<%=url('admin/services/openvpn/file', self.instance)%>"><%:Switch to file based configuration%> &#187;</a>
                <hr />
                <%:Configuration category%>:
                <% for i, c in ipairs(self.categories) do %>
index dcda941f7bfab8f84ff04ea5ff07bc03b5a2ee9d..04c460182fbd40af6488eb84dd530737cf68a4b7 100644 (file)
@@ -74,6 +74,7 @@
                                return XHR.get(url, data, cb);
                },
 
+               stop: function(entry) { XHR.stop(entry) },
                halt: function() { XHR.halt() },
                run: function() { XHR.run() },
 
        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);
index c4066a25920c26ff2c9d7113405096866238bcff..d85cb58243082e007a3144ca2ee03bbc50e2aca0 100644 (file)
@@ -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
index f26256953a4a1433916b5149ebb33d4d829a0b19..a77f8d8b0732b0491b680a295b3c31d2893ce3f3 100644 (file)
@@ -81,15 +81,6 @@ Build the index before if it does not exist yet.
 @name createtree
 ]]
 
----[[
-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.
 
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 (file)
index 0000000..88f48d1
--- /dev/null
@@ -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 (file)
index 0000000..d5bd7b0
--- /dev/null
@@ -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 (file)
index 0000000..7e14d99
--- /dev/null
@@ -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();
+       });
+});
index a200f79b51680e3ea8d03708df776ebc8ad9bd91..1da5eac4643c2922a70e691e5703187df740365f 100644 (file)
@@ -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()
index 8ed39df4861cdad6c55142faaf44c0a5988c6967..9ab282c3ab1cd49b6ee11359d2ec289514401ae7 100644 (file)
@@ -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
 
index 34be35dd20ffd203a832401635f61f11cf0cb60b..a75b2755cde37b63260e3221cc21ec2014154ec2 100644 (file)
@@ -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%>
index 987123642fb98aa319dbae38c34661309432c4fb..5a61ba099c2d3f70835563dc6bbe5b2a9422d974 100644 (file)
 
 <%+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">&#160;</div>
                        </div>
 
        </form>
 </div>
 
+<script type="text/javascript" src="<%=resource%>/view/network/wifi_join.js"></script>
+
 <%+footer%>
index bfad3d0804784947712d2a6976e13912eb861fed..93ae2f51fbd3a82c1a4772ae2db137eb14aeaddf 100644 (file)
@@ -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%>" />&#160;<br />%d%%&#160;',
-                                               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%>" />&#160;
        </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%>
index 77efa11a0f935e942076496deb837398e411d942..e0917995e43454dde354f293125d9372180d9062 100644 (file)
@@ -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">
index 2322a738574eb81fa6e25688036aa66ed7fb9b00..98f6022ca0b8aeb65e906177026f4d6138617690 100644 (file)
@@ -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,
index b74f2090452c502a58490ba97817c9ef728d9f65..062d274b7504e436a7970d0e47211ad0f6cdd488 100644 (file)
@@ -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;
+       }
+}
old mode 100755 (executable)
new mode 100644 (file)
index b407d18..72c3115
Binary files a/themes/luci-theme-material/htdocs/luci-static/material/favicon.ico and b/themes/luci-theme-material/htdocs/luci-static/material/favicon.ico differ
diff --git a/themes/luci-theme-material/htdocs/luci-static/material/logo.png b/themes/luci-theme-material/htdocs/luci-static/material/logo.png
deleted file mode 100755 (executable)
index 459148c..0000000
Binary files a/themes/luci-theme-material/htdocs/luci-static/material/logo.png and /dev/null differ
index f6ea9645ff56c2feed6e460641193d1b5f84ac84..f8133833e8c10d2bd3ee7c206506b18361e9403a 100644 (file)
@@ -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 {