summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--applications/luci-app-openvpn/luasrc/model/cbi/openvpn-advanced.lua70
-rw-r--r--applications/luci-app-openvpn/luasrc/model/cbi/openvpn-basic.lua4
-rw-r--r--applications/luci-app-openvpn/luasrc/model/cbi/openvpn-file.lua43
-rw-r--r--applications/luci-app-openvpn/luasrc/model/cbi/openvpn.lua37
-rw-r--r--applications/luci-app-openvpn/luasrc/view/openvpn/cbi-select-input-add.htm16
-rw-r--r--applications/luci-app-openvpn/luasrc/view/openvpn/ovpn_css.htm6
-rw-r--r--applications/luci-app-openvpn/luasrc/view/openvpn/pageswitch.htm8
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/luci.js6
-rw-r--r--modules/luci-base/luasrc/dispatcher.lua82
-rw-r--r--modules/luci-base/luasrc/dispatcher.luadoc9
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/iface_status.js42
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js159
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js59
-rw-r--r--modules/luci-mod-network/luasrc/controller/admin/network.lua2
-rw-r--r--modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua42
-rw-r--r--modules/luci-mod-network/luasrc/view/admin_network/iface_status.htm62
-rw-r--r--modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm183
-rw-r--r--modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm71
-rw-r--r--modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm13
-rw-r--r--themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/cascade.css108
-rw-r--r--themes/luci-theme-bootstrap/htdocs/luci-static/bootstrap/mobile.css387
-rw-r--r--[-rwxr-xr-x]themes/luci-theme-material/htdocs/luci-static/material/favicon.icobin2462 -> 4286 bytes
-rwxr-xr-xthemes/luci-theme-material/htdocs/luci-static/material/logo.pngbin2224 -> 0 bytes
-rw-r--r--themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css2
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> &#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 %>
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">&#160;</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%>" />&#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%>
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
index 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
Binary files 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
index 459148c6b6..0000000000
--- a/themes/luci-theme-material/htdocs/luci-static/material/logo.png
+++ /dev/null
Binary files differ
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 {