Merge pull request #644 from chris5560/master
authorChristian Schoenebeck <christian.schoenebeck@gmail.com>
Thu, 18 Feb 2016 18:25:41 +0000 (19:25 +0100)
committerChristian Schoenebeck <christian.schoenebeck@gmail.com>
Thu, 18 Feb 2016 18:25:41 +0000 (19:25 +0100)
luci-base: fixed tabbed map when using NamedSection of same type

23 files changed:
applications/luci-app-adblock/Makefile [new file with mode: 0644]
applications/luci-app-adblock/luasrc/controller/adblock.lua [new file with mode: 0644]
applications/luci-app-adblock/luasrc/model/cbi/adblock.lua [new file with mode: 0644]
applications/luci-app-adblock/root/etc/uci-defaults/40_luci-adblock [new file with mode: 0755]
applications/luci-app-firewall/luasrc/view/firewall/cbi_addforward.htm
applications/luci-app-firewall/luasrc/view/firewall/cbi_addsnat.htm
modules/luci-base/htdocs/luci-static/resources/cbi.js
modules/luci-base/luasrc/dispatcher.lua
modules/luci-base/luasrc/view/cbi/dynlist.htm
modules/luci-base/luasrc/view/cbi/header.htm
modules/luci-base/luasrc/view/cbi/lvalue.htm
modules/luci-base/luasrc/view/cbi/mvalue.htm
modules/luci-base/luasrc/view/cbi/nsection.htm
modules/luci-base/luasrc/view/cbi/tblsection.htm
modules/luci-base/luasrc/view/cbi/tsection.htm
modules/luci-base/luasrc/view/cbi/ucisection.htm
modules/luci-base/luasrc/view/cbi/value.htm
modules/luci-mod-admin-full/luasrc/view/admin_system/packages.htm
themes/luci-theme-material/htdocs/luci-static/material/css/style.css
themes/luci-theme-material/htdocs/luci-static/material/js/script.js
themes/luci-theme-material/luasrc/view/themes/material/header.htm
themes/luci-theme-openwrt/htdocs/luci-static/openwrt.org/cascade.css
themes/luci-theme-openwrt/luasrc/view/themes/openwrt.org/header.htm

diff --git a/applications/luci-app-adblock/Makefile b/applications/luci-app-adblock/Makefile
new file mode 100644 (file)
index 0000000..8efe2d6
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright (C) 2016 Openwrt.org
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for Adblock
+LUCI_DEPENDS:=+adblock
+LUCI_PKGARCH:=all
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/applications/luci-app-adblock/luasrc/controller/adblock.lua b/applications/luci-app-adblock/luasrc/controller/adblock.lua
new file mode 100644 (file)
index 0000000..d8b4718
--- /dev/null
@@ -0,0 +1,12 @@
+-- Copyright 2016 Openwrt.org
+-- Licensed to the public under the Apache License 2.0.
+
+module("luci.controller.adblock", package.seeall)
+
+function index()
+       if not nixio.fs.access("/etc/config/adblock") then
+               return
+       end
+
+       entry({"admin", "services", "adblock"}, cbi("adblock"), _("Adblock"), 40)
+end
diff --git a/applications/luci-app-adblock/luasrc/model/cbi/adblock.lua b/applications/luci-app-adblock/luasrc/model/cbi/adblock.lua
new file mode 100644 (file)
index 0000000..1fd77b4
--- /dev/null
@@ -0,0 +1,60 @@
+-- Copyright 2016 Openwrt.org
+-- Licensed to the public under the Apache License 2.0.
+
+m = Map("adblock", translate("Adblock"),
+       translate("Configuration of the adblock package to block ad/abuse domains by using DNS."))
+
+-- General options
+
+s = m:section(NamedSection, "global", "adblock", translate("Global options"))
+
+o1 = s:option(Flag, "adb_enabled", translate("Enable"))
+o1.rmempty = false
+o1.default = 0
+
+o2 = s:option(Value, "adb_blacklist", translate("Blacklist file"),
+     translate("File with explicitly blacklisted hosts/domains."))
+o2.rmempty = false
+o2.datatype = "file"
+
+o3 = s:option(Value, "adb_whitelist", translate("Whitelist file"),
+     translate("File with whitelisted hosts/domains that are allowed despite being on a blocklist."))
+o3.rmempty = false
+o3.datatype = "file"
+
+s2 = m:section(NamedSection, "backup", "service", translate("Backup options"))
+
+o4 = s2:option(Flag, "enabled", translate("Enable blocklist backup"))
+o4.rmempty = false
+o4.default = 0
+
+o5 = s2:option(Value, "adb_backupdir", translate("Backup directory"))
+o5.rmempty = false
+o5.datatype = "directory"
+
+s3 = m:section(NamedSection, "debuglog", "service", translate("Debug log options"),
+       translate("Verbose log for debuging purposes."))
+
+o6 = s3:option(Flag, "enabled", translate("Enable debug log"))
+o6.rmempty = false
+o6.default = 0
+
+o7 = s3:option(Value, "adb_logfile", translate("Debug log file"))
+o7.rmempty = false
+o7.datatype = "string"
+
+-- Blocklist options
+
+s3 = m:section(TypedSection, "source", translate("Blocklist sources"),
+       translate("Available blocklist sources (")
+       .. [[<a href="https://github.com/openwrt/packages/blob/master/net/adblock/files/README.md#main-features" target="_blank">]]
+       .. translate("see list details")
+       .. [[</a>]]
+       .. translate("). Note that list URLs and Shallalist category selections are not configurable via Luci."))
+
+name = s3:option(Flag, "enabled", translate("Enabled"))
+name.rmempty  = false
+
+
+return m
+
diff --git a/applications/luci-app-adblock/root/etc/uci-defaults/40_luci-adblock b/applications/luci-app-adblock/root/etc/uci-defaults/40_luci-adblock
new file mode 100755 (executable)
index 0000000..1f7fb1c
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+       delete ucitrack.@adblock[-1]
+       add ucitrack adblock
+       set ucitrack.@adblock[-1].init=adblock
+       commit ucitrack
+EOF
+
+rm -f /tmp/luci-indexcache
+exit 0
index 3c46e22..b3079f3 100644 (file)
                        ezl[#ezl+1] = z
                end
        end
+
+       local keys, vals = { }, { }
+       luci.sys.net.ipv4_hints(function(ip, name)
+               keys[#keys+1] = ip
+               vals[#vals+1] = '%s (%s)' %{ ip, name }
+       end)
 -%>
 <div class="cbi-section-create cbi-tblsection-create">
        <br />
@@ -46,7 +52,7 @@
                                </select>
                        </td>
                        <td class="cbi-section-table-cell" style="width:110px">
-                               <input type="text" class="cbi-input-text" id="_newfwd.extport" name="_newfwd.extport" />
+                               <input type="text" class="cbi-input-text" id="_newfwd.extport" name="_newfwd.extport" data-type="portrange" data-optional="true" />
                        </td>
                        <td class="cbi-section-table-cell" style="width:55px">
                                <select class="cbi-input-select" id="_newfwd.intzone" name="_newfwd.intzone">
                                </select>
                        </td>
                        <td class="cbi-section-table-cell" style="width:110px">
-                               <input type="text" class="cbi-input-text" id="_newfwd.intaddr" name="_newfwd.intaddr" />
+                               <input type="text" class="cbi-input-text" id="_newfwd.intaddr" name="_newfwd.intaddr" data-type="host" data-optional="true"<%=
+                                       ifattr(#keys > 0, "data-choices", {keys, vals})
+                               %>/>
                        </td>
                        <td class="cbi-section-table-cell" style="width:110px">
-                               <input type="text" class="cbi-input-text" id="_newfwd.intport" name="_newfwd.intport" />
+                               <input type="text" class="cbi-input-text" id="_newfwd.intport" name="_newfwd.intport" data-type="portrange" data-optional="true" />
                        </td>
                        <td class="cbi-section-table-cell">
                                <input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" value="<%:Add%>" />
        </table>
 
        <script type="text/javascript">//<![CDATA[
-               cbi_validate_field('_newfwd.extport', true, 'portrange');
-               cbi_validate_field('_newfwd.intaddr', true, 'host');
-               cbi_validate_field('_newfwd.intport', true, 'portrange');
-
-               cbi_combobox_init('_newfwd.intaddr', {
-                       <% first = true; luci.sys.net.ipv4_hints(function(ip, name) %>
-                               <%- if first then first = false else %>,<% end -%>'<%=ip%>': '<%=ip%> (<%=name%>)'
-                       <%- end) %> }, '', '<%: -- custom -- %>');
-
                cbi_bind(document.getElementById('_newfwd.extport'), 'blur',
                        function() {
                                var n = document.getElementById('_newfwd.name');
                                        }
                                }
                        });
-
-
-               cbi_validate_field('cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>', true, 'uciname');
        //]]></script>
 </div>
index 4e1681c..ce27511 100644 (file)
@@ -3,6 +3,14 @@
        local nw = require "luci.model.network".init()
        local wz = fw:get_zone("wan")
        local lz = fw:get_zone("lan")
+
+       local keys, vals, a, k, v = {}, {}
+       for k, v in ipairs(nw:get_interfaces()) do
+               for k, a in ipairs(v:ipaddrs()) do
+                       keys[#keys+1] = a:host():string()
+                       vals[#vals+1] = '%s (%s)' %{ a:host(), v:shortname() }
+               end
+       end
 %>
 
 <div class="cbi-section-create cbi-tblsection-create">
                                        </select>
                                </td>
                                <td class="cbi-section-table-cell" style="width:110px">
-                                       <input type="text" class="cbi-input-text" id="_newsnat.dip" name="_newsnat.dip" />
+                                       <input type="text" class="cbi-input-text" id="_newsnat.dip" name="_newsnat.dip" placeholder="<%:Do not rewrite%>" data-type="ip4addr" data-optional="true"<%=
+                                               ifattr(#keys > 0, "data-choices", { keys, vals })
+                                       %> />
                                </td>
                                <td class="cbi-section-table-cell" style="width:110px">
-                                       <input type="text" class="cbi-input-text" id="_newsnat.dport" name="_newsnat.dport" placeholder="<%:Do not rewrite%>" />
+                                       <input type="text" class="cbi-input-text" id="_newsnat.dport" name="_newsnat.dport" placeholder="<%:Do not rewrite%>" data-type="portrange" data-optional="true" />
                                </td>
                                <td class="cbi-section-table-cell">
                                        <input type="submit" class="cbi-button cbi-button-link" name="_newsnat.submit" value="<%:Add and edit...%>" />
                                </td>
                        </tr>
                </table>
-
-               <script type="text/javascript">//<![CDATA[
-                       cbi_validate_field('_newsnat.dport', true, 'portrange');
-                       cbi_validate_field('_newsnat.dip', true, 'ip4addr');
-                       cbi_combobox_init('_newsnat.dip', {
-                               <% local c, k, v = 0; for k, v in ipairs(nw:get_interfaces()) do -%>
-                                       <%- local a; for k, a in ipairs(v:ipaddrs()) do c = c + 1 -%>
-                                               <% if c > 1 then %>,<% end %>'<%=a:host():string()%>': '<%=a:host():string()%> (<%=v:shortname()%>)'
-                                       <%- end %>
-                               <%- end %> }, '<%: -- Please choose -- %>', '<%: -- custom -- %>');
-               //]]></script>
        <% else %>
                <input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" value="<%:Add%>" />
        <% end %>
index 2274610..26fd924 100644 (file)
 
 var cbi_d = [];
 var cbi_t = [];
+var cbi_strings = { path: {}, label: {} };
+
+function Int(x) {
+       return (/^-?\d+$/.test(x) ? +x : NaN);
+}
+
+function Dec(x) {
+       return (/^-?\d+(?:\.\d+)?$/.test(x) ? +x : NaN);
+}
 
 var cbi_validators = {
 
        'integer': function()
        {
-               return (this.match(/^-?[0-9]+$/) != null);
+               return !!Int(this);
        },
 
        'uinteger': function()
        {
-               return (cbi_validators.integer.apply(this) && (this >= 0));
+               return (Int(this) >= 0);
        },
 
        'float': function()
        {
-               return !isNaN(parseFloat(this));
+               return !!Dec(this);
        },
 
        'ufloat': function()
        {
-               return (cbi_validators['float'].apply(this) && (this >= 0));
+               return (Dec(this) >= 0);
        },
 
        'ipaddr': function()
@@ -111,26 +120,20 @@ var cbi_validators = {
 
        'port': function()
        {
-               return cbi_validators.integer.apply(this) &&
-                       (this >= 0) && (this <= 65535);
+               var p = Int(this);
+               return (p >= 0 && p <= 65535);
        },
 
        'portrange': function()
        {
                if (this.match(/^(\d+)-(\d+)$/))
                {
-                       var p1 = RegExp.$1;
-                       var p2 = RegExp.$2;
-
-                       return cbi_validators.port.apply(p1) &&
-                              cbi_validators.port.apply(p2) &&
-                              (parseInt(p1) <= parseInt(p2))
-                       ;
-               }
-               else
-               {
-                       return cbi_validators.port.apply(this);
+                       var p1 = +RegExp.$1;
+                       var p2 = +RegExp.$2;
+                       return (p1 <= p2 && p2 <= 65535);
                }
+
+               return cbi_validators.port.apply(this);
        },
 
        'macaddr': function()
@@ -234,56 +237,34 @@ var cbi_validators = {
 
        'range': function(min, max)
        {
-               var val = parseFloat(this);
-               if (!isNaN(min) && !isNaN(max) && !isNaN(val))
-                       return ((val >= min) && (val <= max));
-
-               return false;
+               var val = Dec(this);
+               return (val >= +min && val <= +max);
        },
 
        'min': function(min)
        {
-               var val = parseFloat(this);
-               if (!isNaN(min) && !isNaN(val))
-                       return (val >= min);
-
-               return false;
+               return (Dec(this) >= +min);
        },
 
        'max': function(max)
        {
-               var val = parseFloat(this);
-               if (!isNaN(max) && !isNaN(val))
-                       return (val <= max);
-
-               return false;
+               return (Dec(this) <= +max);
        },
 
        'rangelength': function(min, max)
        {
                var val = '' + this;
-               if (!isNaN(min) && !isNaN(max))
-                       return ((val.length >= min) && (val.length <= max));
-
-               return false;
+               return ((val.length >= +min) && (val.length <= +max));
        },
 
        'minlength': function(min)
        {
-               var val = '' + this;
-               if (!isNaN(min))
-                       return (val.length >= min);
-
-               return false;
+               return ((''+this).length >= +min);
        },
 
        'maxlength': function(max)
        {
-               var val = '' + this;
-               if (!isNaN(max))
-                       return (val.length <= max);
-
-               return false;
+               return ((''+this).length <= +max);
        },
 
        'or': function()
@@ -473,7 +454,7 @@ function cbi_d_update() {
                if (node && node.parentNode && !cbi_d_check(entry.deps)) {
                        node.parentNode.removeChild(node);
                        state = true;
-               } else if ((!node || !node.parentNode) && cbi_d_check(entry.deps)) {
+               } else if (parent && (!node || !node.parentNode) && cbi_d_check(entry.deps)) {
                        var next = undefined;
 
                        for (next = parent.firstChild; next; next = next.nextSibling) {
@@ -490,6 +471,10 @@ function cbi_d_update() {
 
                        state = true;
                }
+
+               // hide optionals widget if no choices remaining
+               if (parent && parent.parentNode && parent.getAttribute('data-optionals'))
+                       parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : '';
        }
 
        if (entry && entry.parent) {
@@ -503,7 +488,21 @@ function cbi_d_update() {
 }
 
 function cbi_init() {
-       var nodes = document.querySelectorAll('[data-depends]');
+       var nodes;
+
+       nodes = document.querySelectorAll('[data-strings]');
+
+       for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
+               var str = JSON.parse(node.getAttribute('data-strings'));
+               for (var key in str) {
+                       for (var key2 in str[key]) {
+                               var dst = cbi_strings[key] || (cbi_strings[key] = { });
+                                   dst[key2] = str[key][key2];
+                       }
+               }
+       }
+
+       nodes = document.querySelectorAll('[data-depends]');
 
        for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
                var index = parseInt(node.getAttribute('data-index'), 10);
@@ -524,6 +523,45 @@ function cbi_init() {
                }
        }
 
+       nodes = document.querySelectorAll('[data-type]');
+
+       for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
+               cbi_validate_field(node, node.getAttribute('data-optional') === 'true',
+                                  node.getAttribute('data-type'));
+       }
+
+       nodes = document.querySelectorAll('[data-choices]');
+
+       for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
+               var choices = JSON.parse(node.getAttribute('data-choices'));
+               var options = {};
+
+               for (var j = 0; j < choices[0].length; j++)
+                       options[choices[0][j]] = choices[1][j];
+
+               var def = (node.getAttribute('data-optional') === 'true')
+                       ? node.placeholder || '' : null;
+
+               cbi_combobox_init(node, options, def,
+                                 node.getAttribute('data-manual'));
+       }
+
+       nodes = document.querySelectorAll('[data-dynlist]');
+
+       for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
+               var choices = JSON.parse(node.getAttribute('data-dynlist'));
+               var options = null;
+
+               if (choices[0] && choices[0].length) {
+                       options = {};
+
+                       for (var j = 0; j < choices[0].length; j++)
+                               options[choices[0][j]] = choices[1][j];
+               }
+
+               cbi_dynlist_init(node, choices[2], choices[3], options);
+       }
+
        cbi_d_update();
 }
 
@@ -572,7 +610,7 @@ function cbi_combobox(id, values, def, man, focus) {
                if (obj.value == "") {
                        var optdef = document.createElement("option");
                        optdef.value = "";
-                       optdef.appendChild(document.createTextNode(def));
+                       optdef.appendChild(document.createTextNode(typeof(def) === 'string' ? def : cbi_strings.label.choose));
                        sel.appendChild(optdef);
                } else {
                        var opt = document.createElement("option");
@@ -597,7 +635,7 @@ function cbi_combobox(id, values, def, man, focus) {
 
        var optman = document.createElement("option");
        optman.value = "";
-       optman.appendChild(document.createTextNode(man));
+       optman.appendChild(document.createTextNode(typeof(man) === 'string' ? man : cbi_strings.label.custom));
        sel.appendChild(optman);
 
        obj.style.display = "none";
@@ -627,27 +665,27 @@ function cbi_combobox(id, values, def, man, focus) {
 }
 
 function cbi_combobox_init(id, values, def, man) {
-       var obj = document.getElementById(id);
+       var obj = (typeof(id) === 'string') ? document.getElementById(id) : id;
        cbi_bind(obj, "blur", function() {
-               cbi_combobox(id, values, def, man, true);
+               cbi_combobox(obj.id, values, def, man, true);
        });
-       cbi_combobox(id, values, def, man, false);
+       cbi_combobox(obj.id, values, def, man, false);
 }
 
-function cbi_filebrowser(id, url, defpath) {
+function cbi_filebrowser(id, defpath) {
        var field   = document.getElementById(id);
        var browser = window.open(
-               url + ( field.value || defpath || '' ) + '?field=' + id,
+               cbi_strings.path.browser + ( field.value || defpath || '' ) + '?field=' + id,
                "luci_filebrowser", "width=300,height=400,left=100,top=200,scrollbars=yes"
        );
 
        browser.focus();
 }
 
-function cbi_browser_init(id, respath, url, defpath)
+function cbi_browser_init(id, defpath)
 {
        function cbi_browser_btnclick(e) {
-               cbi_filebrowser(id, url, defpath);
+               cbi_filebrowser(id, defpath);
                return false;
        }
 
@@ -655,18 +693,16 @@ function cbi_browser_init(id, respath, url, defpath)
 
        var btn = document.createElement('img');
        btn.className = 'cbi-image-button';
-       btn.src = respath + '/cbi/folder.gif';
+       btn.src = cbi_strings.path.resource + '/cbi/folder.gif';
        field.parentNode.insertBefore(btn, field.nextSibling);
 
        cbi_bind(btn, 'click', cbi_browser_btnclick);
 }
 
-function cbi_dynlist_init(name, respath, datatype, optional, url, defpath, choices)
+function cbi_dynlist_init(parent, datatype, optional, choices)
 {
-       var input0 = document.getElementsByName(name)[0];
-       var prefix = input0.name;
-       var parent = input0.parentNode;
-       var holder = input0.placeholder;
+       var prefix = parent.getAttribute('data-prefix');
+       var holder = parent.getAttribute('data-placeholder');
 
        var values;
 
@@ -677,7 +713,7 @@ function cbi_dynlist_init(name, respath, datatype, optional, url, defpath, choic
                while (parent.firstChild)
                {
                        var n = parent.firstChild;
-                       var i = parseInt(n.index);
+                       var i = +n.index;
 
                        if (i != del)
                        {
@@ -717,14 +753,14 @@ function cbi_dynlist_init(name, respath, datatype, optional, url, defpath, choic
                        }
 
                        var b = document.createElement('img');
-                               b.src = respath + ((i+1) < values.length ? '/cbi/remove.gif' : '/cbi/add.gif');
+                               b.src = cbi_strings.path.resource + ((i+1) < values.length ? '/cbi/remove.gif' : '/cbi/add.gif');
                                b.className = 'cbi-image-button';
 
                        parent.appendChild(t);
                        parent.appendChild(b);
                        if (datatype == 'file')
                        {
-                               cbi_browser_init(t.id, respath, url, defpath);
+                               cbi_browser_init(t.id, parent.getAttribute('data-browser-path'));
                        }
 
                        parent.appendChild(document.createElement('br'));
@@ -736,7 +772,7 @@ function cbi_dynlist_init(name, respath, datatype, optional, url, defpath, choic
 
                        if (choices)
                        {
-                               cbi_combobox_init(t.id, choices[0], '', choices[1]);
+                               cbi_combobox_init(t.id, choices, '', cbi_strings.label.custom);
                                b.index = i;
 
                                cbi_bind(b, 'keydown',  cbi_dynlist_keydown);
@@ -816,15 +852,15 @@ function cbi_dynlist_init(name, respath, datatype, optional, url, defpath, choic
                        se = se.parentNode;
 
                var prev = se.previousSibling;
-               while (prev && prev.name != name)
+               while (prev && prev.name != prefix)
                        prev = prev.previousSibling;
 
                var next = se.nextSibling;
-               while (next && next.name != name)
+               while (next && next.name != prefix)
                        next = next.nextSibling;
 
                /* advance one further in combobox case */
-               if (next && next.nextSibling.name == name)
+               if (next && next.nextSibling.name == prefix)
                        next = next.nextSibling;
 
                switch (ev.keyCode)
@@ -880,7 +916,7 @@ function cbi_dynlist_init(name, respath, datatype, optional, url, defpath, choic
 
                var se = ev.target ? ev.target : ev.srcElement;
                var input = se.previousSibling;
-               while (input && input.name != name) {
+               while (input && input.name != prefix) {
                        input = input.previousSibling;
                }
 
@@ -889,14 +925,14 @@ function cbi_dynlist_init(name, respath, datatype, optional, url, defpath, choic
                        input.value = '';
 
                        cbi_dynlist_keydown({
-                               target:  input,
+                               target:  se,
                                keyCode: 8
                        });
                }
                else
                {
                        cbi_dynlist_keydown({
-                               target:  input,
+                               target:  se,
                                keyCode: 13
                        });
                }
@@ -1273,7 +1309,7 @@ String.prototype.format = function()
        var re = /^(([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X|q|h|j|t|m))/;
        var a = b = [], numSubstitutions = 0, numMatches = 0;
 
-       while( a = re.exec(str) )
+       while (a = re.exec(str))
        {
                var m = a[1];
                var leftpart = a[2], pPad = a[3], pJustify = a[4], pMinLength = a[5];
@@ -1296,6 +1332,8 @@ String.prototype.format = function()
                                        pad = leftpart.substr(1,1);
                                else if (pPad)
                                        pad = pPad;
+                               else
+                                       pad = ' ';
 
                                var justifyRight = true;
                                if (pJustify && pJustify === "-")
@@ -1303,40 +1341,40 @@ String.prototype.format = function()
 
                                var minLength = -1;
                                if (pMinLength)
-                                       minLength = parseInt(pMinLength);
+                                       minLength = +pMinLength;
 
                                var precision = -1;
                                if (pPrecision && pType == 'f')
-                                       precision = parseInt(pPrecision.substring(1));
+                                       precision = +pPrecision.substring(1);
 
                                var subst = param;
 
                                switch(pType)
                                {
                                        case 'b':
-                                               subst = (parseInt(param) || 0).toString(2);
+                                               subst = (+param || 0).toString(2);
                                                break;
 
                                        case 'c':
-                                               subst = String.fromCharCode(parseInt(param) || 0);
+                                               subst = String.fromCharCode(+param || 0);
                                                break;
 
                                        case 'd':
-                                               subst = (parseInt(param) || 0);
+                                               subst = ~~(+param || 0);
                                                break;
 
                                        case 'u':
-                                               subst = Math.abs(parseInt(param) || 0);
+                                               subst = ~~Math.abs(+param || 0);
                                                break;
 
                                        case 'f':
                                                subst = (precision > -1)
-                                                       ? ((parseFloat(param) || 0.0)).toFixed(precision)
-                                                       : (parseFloat(param) || 0.0);
+                                                       ? ((+param || 0.0)).toFixed(precision)
+                                                       : (+param || 0.0);
                                                break;
 
                                        case 'o':
-                                               subst = (parseInt(param) || 0).toString(8);
+                                               subst = (+param || 0).toString(8);
                                                break;
 
                                        case 's':
@@ -1344,11 +1382,11 @@ String.prototype.format = function()
                                                break;
 
                                        case 'x':
-                                               subst = ('' + (parseInt(param) || 0).toString(16)).toLowerCase();
+                                               subst = ('' + (+param || 0).toString(16)).toLowerCase();
                                                break;
 
                                        case 'X':
-                                               subst = ('' + (parseInt(param) || 0).toString(16)).toUpperCase();
+                                               subst = ('' + (+param || 0).toString(16)).toUpperCase();
                                                break;
 
                                        case 'h':
@@ -1391,22 +1429,32 @@ String.prototype.format = function()
                                                break;
 
                                        case 'm':
-                                               var mf = pMinLength ? parseInt(pMinLength) : 1000;
-                                               var pr = pPrecision ? Math.floor(10*parseFloat('0'+pPrecision)) : 2;
+                                               var mf = pMinLength ? +pMinLength : 1000;
+                                               var pr = pPrecision ? ~~(10 * +('0' + pPrecision)) : 2;
 
                                                var i = 0;
-                                               var val = parseFloat(param || 0);
-                                               var units = [ '', 'K', 'M', 'G', 'T', 'P', 'E' ];
+                                               var val = (+param || 0);
+                                               var units = [ ' ', ' K', ' M', ' G', ' T', ' P', ' E' ];
 
                                                for (i = 0; (i < units.length) && (val > mf); i++)
                                                        val /= mf;
 
-                                               subst = val.toFixed(pr) + ' ' + units[i];
+                                               subst = (i ? val.toFixed(pr) : val) + units[i];
+                                               pMinLength = null;
                                                break;
                                }
                        }
                }
 
+               if (pMinLength) {
+                       subst = subst.toString();
+                       for (var i = subst.length; i < pMinLength; i++)
+                               if (pJustify == '-')
+                                       subst = subst + ' ';
+                               else
+                                       subst = pad + subst;
+               }
+
                out += leftpart + subst;
                str = str.substr(m.length);
        }
index 91a4c63..c7903e6 100644 (file)
@@ -273,6 +273,13 @@ function dispatch(request)
                        if cond then
                                local env = getfenv(3)
                                local scope = (type(env.self) == "table") and env.self
+                               if type(val) == "table" then
+                                       if not next(val) then
+                                               return ''
+                                       else
+                                               val = util.serialize_json(val)
+                                       end
+                               end
                                return string.format(
                                        ' %s="%s"', tostring(key),
                                        util.pcdata(tostring( val
index 80cbee8..4d0b509 100644 (file)
@@ -1,5 +1,15 @@
 <%+cbi/valueheader%>
-<div>
+<div<%=
+       attr("data-prefix", cbid) ..
+       attr("data-browser-path", self.default_path) ..
+       attr("data-dynlist", luci.util.serialize_json({
+               self.keylist, self.vallist,
+               self.datatype, self.optional or self.rmempty
+       })) ..
+
+       ifattr(self.size, "data-size", self.size) ..
+       ifattr(self.placeholder, "data-placeholder", self.placeholder)
+%>>
 <%
        local vals = self:cfgvalue(section) or {}
        for i=1, #vals + 1 do
                if (val and #val > 0) or (i == 1) then
 %>
        <input class="cbi-input-text" value="<%=pcdata(val)%>" data-update="change" type="text"<%=
-               attr("id", cbid .. "." .. i) .. attr("name", cbid) .. ifattr(self.size, "size") ..
+               attr("id", cbid .. "." .. i) ..
+               attr("name", cbid) ..
+               ifattr(self.size, "size") ..
                ifattr(i == 1 and self.placeholder, "placeholder", self.placeholder)
        %> /><br />
 <% end end %>
 </div>
-<script type="text/javascript">
-cbi_dynlist_init(
-       '<%=cbid%>', '<%=resource%>', '<%=self.datatype%>',
-       <%=tostring(self.optional or self.rmempty)%>,
-       '<%=url('admin/filebrowser')%>',
-       '<%=self.default_path and self.default_path%>'
-       <%- if #self.keylist > 0 then -%>, [{
-               <%- for i, k in ipairs(self.keylist) do -%>
-                       <%-=string.format("%q", k) .. ":" .. string.format("%q", self.vallist[i])-%>
-                       <%-if i<#self.keylist then-%>,<%-end-%>
-               <%-     end     -%>
-       }, '<%: -- custom -- %>']<% end -%>);
-</script>
 <%+cbi/valuefooter%>
index 302df1d..9710bae 100644 (file)
@@ -1,7 +1,18 @@
 <%+header%>
 <form method="post" name="cbi" action="<%=REQUEST_URI%>" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, '<%:Some fields are invalid, cannot save values!%>')">
        <div>
-               <script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+               <script type="text/javascript" src="<%=resource%>/cbi.js"<%=
+                       attr("data-strings", luci.util.serialize_json({
+                               label = {
+                                       choose = translate('-- Please choose --'),
+                                       custom = translate('-- custom --'),
+                               },
+                               path = {
+                                       resource = resource,
+                                       browser  = url("admin/filebrowser")
+                               }
+                       }))
+               %>></script>
                <input type="hidden" name="token" value="<%=token%>" />
                <input type="hidden" name="cbi.submit" value="1" />
                <input type="submit" value="<%:Save%>" class="hidden" />
index ac63e9e..61759fd 100644 (file)
@@ -1,18 +1,42 @@
+<%
+       local i, key
+       local br = self.orientation == "horizontal" and '&#160;' or '<br />'
+%>
+
 <%+cbi/valueheader%>
 <% if self.widget == "select" then %>
-       <select class="cbi-input-select" data-update="change"<%= attr("id", cbid) .. attr("name", cbid) .. ifattr(self.size, "size") %>>
-       <% for i, key in pairs(self.keylist) do -%>
-               <option id="cbi-<%=self.config.."-"..section.."-"..self.option.."-"..key%>"<%= attr("value", key) .. ifattr(tostring(self:cfgvalue(section) or self.default) == key, "selected", "selected") .. attr("data-index", i) .. attr("data-depends", self:deplist2json(section, self.deplist[i])) %>><%=striptags(self.vallist[i])%></option>
-       <%- end %>
+       <select class="cbi-input-select" data-update="change"<%=
+               attr("id", cbid) ..
+               attr("name", cbid) ..
+               ifattr(self.size, "size")
+       %>>
+               <% for i, key in pairs(self.keylist) do -%>
+                       <option<%=
+                               attr("id", cbid.."-"..key) ..
+                               attr("value", key) ..
+                               attr("data-index", i) ..
+                               attr("data-depends", self:deplist2json(section, self.deplist[i])) ..
+                               ifattr(tostring(self:cfgvalue(section) or self.default) == key, "selected", "selected")
+                       %>><%=pcdata(self.vallist[i])%></option>
+               <%- end %>
        </select>
-<% elseif self.widget == "radio" then
-       local c = 0
-       for i, key in pairs(self.keylist) do
-       c = c + 1
-%>
-       <input class="cbi-input-radio" data-update="click change" type="radio"<%= attr("id", cbid.."-"..key) .. attr("name", cbid) .. attr("value", key) .. ifattr((self:cfgvalue(section) or self.default) == key, "checked", "checked") .. attr("data-index", i) .. attr("data-depends", self:deplist2json(section, self.deplist[i])) %> />
-       <label<%= attr("for", cbid.."-"..key) %>><%=self.vallist[i]%></label>
-<% if c == self.size then c = 0 %><% if self.orientation == "horizontal" then %>&#160;<% else %><br /><% end %>
-<% end end %>
+<% elseif self.widget == "radio" then %>
+       <div<%= attr("id", cbid) %>>
+               <% for i, key in pairs(self.keylist) do %>
+                       <label<%=
+                               attr("id", cbid.."-"..key) ..
+                               attr("data-index", i) ..
+                               attr("data-depends", self:deplist2json(section, self.deplist[i]))
+                       %>>
+                               <input class="cbi-input-radio" data-update="click change" type="radio"<%=
+                                       attr("name", cbid) ..
+                                       attr("value", key) ..
+                                       ifattr((self:cfgvalue(section) or self.default) == key, "checked", "checked")
+                               %> />
+                               <%=pcdata(self.vallist[i])%>
+                       </label>
+                       <% if i == self.size then write(br) end %>
+               <% end %>
+       </div>
 <% end %>
 <%+cbi/valuefooter%>
index 79950ce..ccdd6fc 100644 (file)
@@ -1,19 +1,42 @@
-<% local v = self:valuelist(section) or {} -%>
+<%
+       local i, key
+       local v = self:valuelist(section) or {}
+-%>
+
 <%+cbi/valueheader%>
 <% if self.widget == "select" then %>
-       <select class="cbi-input-select" multiple="multiple" data-update="click change"<%= attr("name", cbid) .. ifattr(self.size, "size") %>>
-       <% for i, key in pairs(self.keylist) do -%>
-               <option<%= attr("id", cbid.."-"..key) .. attr("value", key) .. ifattr(luci.util.contains(v, key), "selected", "selected") .. attr("data-index", i) .. attr("data-depends", self:deplist2json(section, self.deplist[i])) %>><%=striptags(self.vallist[i])%></option>
-       <%- end %>
+       <select class="cbi-input-select" multiple="multiple" data-update="click change"<%=
+               attr("id", cbid) ..
+               attr("name", cbid) ..
+               ifattr(self.size, "size")
+       %>>
+               <% for i, key in pairs(self.keylist) do -%>
+                       <option<%=
+                               attr("id", cbid.."-"..key) ..
+                               attr("value", key) ..
+                               attr("data-index", i) ..
+                               attr("data-depends", self:deplist2json(section, self.deplist[i])) ..
+                               ifattr(luci.util.contains(v, key), "selected", "selected")
+                       %>><%=pcdata(self.vallist[i])%></option>
+               <%- end %>
        </select>
-<% elseif self.widget == "checkbox" then
-       local c = 0;
-       for i, key in pairs(self.keylist) do
-       c = c + 1
-%>
-       <input class="cbi-input-checkbox" type="checkbox" data-update="click change"<%= attr("id", cbid.."-"..key) .. attr("name", cbid) .. attr("value", key) .. ifattr(luci.util.contains(v, key), "checked", "checked") .. attr("data-index", i) .. attr("data-depends", self:deplist2json(section, self.deplist[i])) %> />
-       <label<%= attr("for", cbid.."-"..key) %>><%=self.vallist[i]%></label><br />
-<% if c == self.size then c = 0 %><br />
-<% end end %>
+<% elseif self.widget == "checkbox" then %>
+       <div<%= attr("id", cbid) %>>
+               <% for i, key in pairs(self.keylist) do %>
+                       <label<%=
+                               attr("id", cbid.."-"..key) ..
+                               attr("data-index", i) ..
+                               attr("data-depends", self:deplist2json(section, self.deplist[i]))
+                       %>>
+                               <input class="cbi-input-checkbox" type="checkbox" data-update="click change"<%=
+                                       attr("name", cbid) ..
+                                       attr("value", key) ..
+                                       ifattr(luci.util.contains(v, key), "checked", "checked")
+                               %> />
+                               <%=pcdata(self.vallist[i])%>
+                       </label>
+                       <% if i == self.size then write('<br />') end %>
+               <% end %>
+       </div>
 <% end %>
 <%+cbi/valuefooter%>
index 32e73e7..abf6759 100644 (file)
@@ -1,5 +1,5 @@
 <% if self:cfgvalue(self.section) then section = self.section %>
-       <fieldset class="cbi-section" id="cbi-<%=self.config%>-<%=section%>">
+       <fieldset class="cbi-section">
                <% if self.title and #self.title > 0 then -%>
                        <legend><%=self.title%></legend>
                <%- end %>
index fcf2161..26d13f9 100644 (file)
@@ -131,8 +131,7 @@ end
                                        <input class="cbi-button cbi-button-add" type="submit" value="<%:Add%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" title="<%:Add%>" />
                                <% else %>
                                        <% if self.invalid_cts then -%><div class="cbi-section-error"><% end %>
-                                       <input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" />
-                                       <script type="text/javascript">cbi_validate_field('cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>', true, 'uciname');</script>
+                                       <input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" data-type="uciname" data-optional="true" />
                                        <input class="cbi-button cbi-button-add" type="submit" onclick="this.form.cbi_state='add-section'; return true" value="<%:Add%>" title="<%:Add%>" />
                                        <% if self.invalid_cts then -%>
                                                <br /><%:Invalid%></div>
index fcffbe0..726521a 100644 (file)
@@ -37,8 +37,7 @@
                                <input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" value="<%:Add%>" />
                        <%- else -%>
                                <% if self.invalid_cts then -%><div class="cbi-section-error"><% end %>
-                               <input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" />
-                               <script type="text/javascript">cbi_validate_field('cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>', true, 'uciname');</script>
+                               <input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" data-type="uciname" data-optional="true" />
                                <input type="submit" class="cbi-button cbi-button-add" onclick="this.form.cbi_state='add-section'; return true" value="<%:Add%>" />
                                <% if self.invalid_cts then -%>
                                        <br /><%:Invalid%></div>
index 3b69f12..2cb1e75 100644 (file)
@@ -15,7 +15,7 @@
 <% end %>
 
 <% if self.error and self.error[section] then -%>
-       <div class="cbi-section-error">
+       <div class="cbi-section-error" data-index="<%=#self.children + 1%>">
                <ul><% for _, e in ipairs(self.error[section]) do -%>
                        <li>
                                <%- if e == "invalid" then -%>
 <%- end %>
 
 <% if self.optionals[section] and #self.optionals[section] > 0 or self.dynamic then %>
-       <div class="cbi-optionals">
-               <% if self.dynamic then %>
-                       <input type="text" id="cbi.opt.<%=self.config%>.<%=section%>" name="cbi.opt.<%=self.config%>.<%=section%>" />
-                       <% if self.optionals[section] and #self.optionals[section] > 0 then %>
-                       <script type="text/javascript">
-                               cbi_combobox_init('cbi.opt.<%=self.config%>.<%=section%>', {
-                               <%-
-                                       for i, val in pairs(self.optionals[section]) do
-                               -%>
-                                       <%-=string.format("%q", val.option) .. ":" .. string.format("%q", striptags(val.title))-%>
-                                       <%-if next(self.optionals[section], i) then-%>,<%-end-%>
-                               <%-
-                                       end
-                               -%>
-                               }, '', '<%-: -- custom -- -%>');
-                       </script>
-                       <% end %>
-               <% else %>
-               <select id="cbi.opt.<%=self.config%>.<%=section%>" name="cbi.opt.<%=self.config%>.<%=section%>">
-                       <option><%: -- Additional Field -- %></option>
-                       <% for key, val in pairs(self.optionals[section]) do -%>
-                               <option id="cbi-<%=self.config.."-"..section.."-"..val.option%>" value="<%=val.option%>"><%=striptags(val.title)%></option>
-                       <%- end %>
-               </select>
-               <script type="text/javascript"><% for key, val in pairs(self.optionals[section]) do %>
-                       <% if #val.deps > 0 then %><% for j, d in ipairs(val.deps) do -%>
-                       cbi_d_add("cbi-<%=self.config.."-"..section.."-"..val.option..d.add%>", {
-               <%-
-                       for k,v in pairs(d.deps) do
-               -%>
-                       <%-=string.format('"cbid.%s.%s.%s"', self.config, section, k) .. ":" .. string.format("%q", v)-%>
-                       <%-if next(d.deps, k) then-%>,<%-end-%>
-               <%-
+       <div class="cbi-optionals" data-index="<%=#self.children + 1%>">
+               <%
+               if self.dynamic then
+                       local keys, vals, name, opt = { }, { }
+                       for name, opt in pairs(self.optionals[section]) do
+                               keys[#keys+1] = name
+                               vals[#vals+1] = opt.title
                        end
-               -%>
-                       });
-               <%- end %><% end %>
-               <% end %></script>
-       <% end %>
+               %>
+                       <input type="text" id="cbi.opt.<%=self.config%>.<%=section%>" name="cbi.opt.<%=self.config%>.<%=section%>" data-type="uciname" data-optional="true"<%=
+                               ifattr(#keys > 0, "data-choices", luci.util.json_encode({keys, vals}))
+                       %> />
+               <% else %>
+                       <select id="cbi.opt.<%=self.config%>.<%=section%>" name="cbi.opt.<%=self.config%>.<%=section%>" data-optionals="true">
+                               <option><%: -- Additional Field -- %></option>
+                               <% for key, val in pairs(self.optionals[section]) do -%>
+                                       <option id="cbi-<%=self.config.."-"..section.."-"..val.option%>" value="<%=val.option%>" data-index="<%=val.index%>" data-depends="<%=pcdata(val:deplist2json(section))%>"><%=striptags(val.title)%></option>
+                               <%- end %>
+                       </select>
+               <% end %>
                <input type="submit" class="cbi-button cbi-button-fieldadd" value="<%:Add%>" />
        </div>
 <% end %>
index 9bb4f3b..c8c905e 100644 (file)
@@ -8,35 +8,11 @@
                ifattr(self.size, "size") ..
                ifattr(self.placeholder, "placeholder") ..
                ifattr(self.readonly, "readonly") ..
-               ifattr(self.maxlength, "maxlength")
+               ifattr(self.maxlength, "maxlength") ..
+               ifattr(self.datatype, "data-type", self.datatype) ..
+               ifattr(self.datatype, "data-optional", self.optional or self.rmempty) ..
+               ifattr(self.combobox_manual, "data-manual", self.combobox_manual) ..
+               ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist })
        %> />
        <% if self.password then %><img src="<%=resource%>/cbi/reload.gif" style="vertical-align:middle" title="<%:Reveal/hide password%>" onclick="var e = document.getElementById('<%=cbid%>'); e.type = (e.type=='password') ? 'text' : 'password';" /><% end %>
-       <% if #self.keylist > 0 or self.datatype then -%>
-       <script type="text/javascript">//<![CDATA[
-               <% if #self.keylist > 0 then -%>
-               cbi_combobox_init('<%=cbid%>', {
-               <%-
-                       for i, k in ipairs(self.keylist) do
-               -%>
-                       <%-=string.format("%q", k) .. ":" .. string.format("%q", self.vallist[i])-%>
-                       <%-if i<#self.keylist then-%>,<%-end-%>
-               <%-
-                       end
-               -%>
-               }, '<%- if not self.rmempty and not self.optional then -%>
-                       <%-: -- Please choose -- -%>
-                       <%- elseif self.placeholder then -%>
-                       <%-= pcdata(self.placeholder) -%>
-               <%- end -%>', '
-               <%- if self.combobox_manual then -%>
-                       <%-=self.combobox_manual-%>
-               <%- else -%>
-                       <%-: -- custom -- -%>
-               <%- end -%>');
-               <%- end %>
-               <% if self.datatype then -%>
-               cbi_validate_field('<%=cbid%>', <%=tostring((self.optional or self.rmempty) == true)%>, '<%=self.datatype:gsub("\\", "\\\\"):gsub("'", "\\'")%>');
-               <%- end %>
-       //]]></script>
-       <% end -%>
 <%+cbi/valuefooter%>
index 1bc9cac..d5d7828 100644 (file)
@@ -102,7 +102,7 @@ end
                                        <label class="cbi-value-title"><%:Download and install package%>:</label>
                                        <div class="cbi-value-field">
                                                <input type="text" name="url" size="30" value="" />
-                                               <input class="cbi-button cbi-input-save" type="submit" name="exec" value="<%:OK%>" />
+                                               <input class="cbi-button cbi-input-save" type="submit" name="go" value="<%:OK%>" />
                                        </div>
                                </div>
 
index 066a86a..545c5fb 100755 (executable)
@@ -57,7 +57,7 @@
 
 .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
     font-family: inherit;
-    font-weight: 500;
+    font-weight: 400;
     line-height: 1.1;
     color: inherit;
 }
@@ -67,16 +67,16 @@ html {
     -ms-text-size-adjust: 100%;
 }
 
+body {
+    font-size: 0.8rem;
+    background-color: #EEE;
+}
+
 html, body {
     margin: 0px;
     padding: 0px;
     height: 100%;
-    font-family: "Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, WenQuanYi Micro Hei, sans-serif;
-    font-size: 62.5%; /* font-size 1rem = 10px on default browser settings */
-}
-
-body {
-    font-size: 1.3rem; /* 1.3rem = 13px */
+    font-family: Microsoft Yahei, WenQuanYi Micro Hei, sans-serif, "Helvetica Neue", Helvetica, Hiragino Sans GB;
 }
 
 select {
@@ -92,23 +92,19 @@ input {
     background-color: transparent;
     color: rgba(0, 0, 0, .87);
     border: none;
-    border-bottom: 1px solid rgba(0, 0, 0, .26);
+    border-bottom: 2px solid rgba(0, 0, 0, .26);
     outline: 0;
     padding: 0;
     box-shadow: none;
     border-radius: 0;
     background-image: none;
-    height: 2rem;
-    font-size: 1.1rem;
+    height: 1.8rem;
+    font-size: 0.8rem;
 }
 
 select:not([multiple="multiple"]):focus,
 input:focus {
-    height: 2rem;
-    height: calc(2rem + 1px);
-    margin-bottom: -1px;
     border-color: #0099CC;
-    border-width: 2px;
 }
 
 select[multiple="multiple"] {
@@ -125,7 +121,7 @@ abbr {
     cursor: help;
 }
 
-hr{
+hr {
     margin: 1rem 0;
     border-color: #EEE;
     opacity: 0.1;
@@ -181,7 +177,7 @@ footer > a {
     text-align: center;
     margin-top: 2rem;
     color: #888;
-    font-size: 1.3rem;
+    font-size: 1.2rem;
 }
 
 .main > .loading > span > .loading-img:before {
@@ -211,18 +207,17 @@ footer > a {
     float: left;
     top: 4rem;
     width: 15%;
-    width: calc(0% + 17rem);
+    width: calc(0% + 15rem);
     height: 100%;
     height: calc(100% - 4rem);
     background-color: white;
-
     overflow-x: auto;
     position: fixed;
 }
 
 .main-right {
     width: 85%;
-    width: calc(100% - 17rem);
+    width: calc(100% - 15rem);
     float: right;
     height: 100%;
     background-color: #EEE;
@@ -251,7 +246,7 @@ header > .container {
 }
 
 header > .container > .brand {
-    font-size: 1.5rem;
+    font-size: 1.4rem;
     color: white;
     text-decoration: none;
     cursor: default;
@@ -308,7 +303,7 @@ header > .container > .brand {
     background-image: none;
     min-width: 6rem;
     padding: 0.5rem 1rem;
-    font-size: 1.1rem;
+    font-size: 0.9rem;
     line-height: 1.42857143;
     color: #fff;
     background-color: #5bc0de;
@@ -349,7 +344,7 @@ header > .container > .brand {
     padding: 0.5rem 1rem;
     text-decoration: none;
     cursor: default;
-    font-size: 1.2rem;
+    font-size: 1.15rem;
 }
 
 .main > .main-left > .nav > li:hover,
@@ -396,21 +391,21 @@ li {
 }
 
 h1 {
-    font-size: 4rem;
+    font-size: 2rem;
     padding-bottom: 10px;
     border-bottom: 1px solid #eee;
 }
 
 h2 {
     margin: 2rem 0 0 0;
-    font-size: 2.5rem;
+    font-size: 1.8rem;
     padding-bottom: 10px;
     border-bottom: 1px solid #eee;
 }
 
 h3 {
     margin: 2rem 0 0 0;
-    font-size: 2rem;
+    font-size: 1.4rem;
     padding-bottom: 10px;
 }
 
@@ -438,6 +433,10 @@ fieldset {
     -webkit-overflow-scrolling: touch;
 }
 
+.cbi-map-descr + fieldset {
+    margin-top: 1rem;
+}
+
 fieldset > legend {
     display: none !important;
 }
@@ -454,8 +453,8 @@ fieldset > fieldset {
     display: block;
     line-height: 1;
     color: #404040;
-    font-size: 1.9rem;
-    padding-bottom: 2rem;
+    font-size: 1.4rem;
+    padding-bottom: 1rem;
     border-bottom: 1px solid #eee;
 }
 
@@ -467,10 +466,8 @@ table {
 }
 
 table > tbody > tr > td, table > tbody > tr > th, table > tfoot > tr > td, table > tfoot > tr > th, table > thead > tr > td, table > thead > tr > th {
-    padding: 8px;
-    line-height: 1.42857143;
+    padding: .5rem;
     border-top: 1px solid #ddd;
-
     white-space: nowrap;
 }
 
@@ -482,7 +479,7 @@ table > tbody > tr > td, table > tbody > tr > th, table > tfoot > tr > td, table
     text-align: center;
 }
 
-fieldset > table > tbody > tr:nth-of-type(odd) {
+fieldset > table > tbody > tr:nth-of-type(2n) {
     background-color: #f9f9f9;
 }
 
@@ -494,7 +491,7 @@ fieldset > table > tbody > tr:nth-of-type(odd) {
 #conns > div,
 #memtotal > div {
     width: 100% !important;
-    height: 1.4rem !important;
+    height: 1.2rem !important;
 }
 
 #swaptotal > div > div,
@@ -503,7 +500,7 @@ fieldset > table > tbody > tr:nth-of-type(odd) {
 #membuff > div > div,
 #conns > div > div,
 #memtotal > div > div {
-    height: 1.4rem !important;
+    height: 100% !important;
     background-color: #0099CC !important;
 }
 
@@ -534,7 +531,7 @@ td > table > tbody > tr > td {
     background-color: #F0F0F0;
     transition: all 0.2s ease-in-out;
     display: inline-block;
-    padding: 0.5rem 1rem;
+    padding: 0 0.8rem;
     border: none;
     border-radius: 0.2rem;
     cursor: pointer;
@@ -548,7 +545,7 @@ td > table > tbody > tr > td {
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    font-size: 1rem;
+    font-size: 0.8rem;
     width: auto !important;
 }
 
@@ -610,6 +607,7 @@ form.inline + form.inline,
 }
 
 .cbi-input-reset,
+.cbi-section-remove > .cbi-button,
 .cbi-button-remove {
     color: #fff !important;
     background-color: #d9534f !important;
@@ -631,14 +629,14 @@ form.inline + form.inline,
 .cbi-tabmenu > li,
 .tabs > li {
     display: inline-block;
-    padding: 0.9rem 0rem;
+    padding: 0.6rem 0rem;
 }
 
 .cbi-tabmenu > li > a,
 .tabs > li > a {
     text-decoration: none;
     color: #404040;
-    padding: 0.9rem 1.5rem;
+    padding: 0.5rem 0.8rem;
 }
 
 .tabs > li[class~="active"],
@@ -675,6 +673,11 @@ form.inline + form.inline,
     background-color: #D4D4D4;
 }
 
+.cbi-section-remove:nth-of-type(2n),
+.cbi-section-node:nth-of-type(2n){
+    background-color: #f9f9f9;
+}
+
 .cbi-section-node-tabbed {
     padding: 0;
     margin-top: 0;
@@ -690,6 +693,7 @@ form.inline + form.inline,
 .cbi-value-field,
 .cbi-value-description {
     display: table-cell;
+    line-height: 1.25;
 }
 
 .cbi-value-helpicon > img {
@@ -717,7 +721,7 @@ form.inline + form.inline,
 }
 
 .cbi-value {
-    padding: 1rem;
+    padding: 0.3rem 1rem;
     display: inline-block;
     width: 100%;
 }
@@ -732,7 +736,7 @@ form.inline + form.inline,
 }
 
 .cbi-rowstyle-2 .cbi-button-up,
-.cbi-rowstyle-2 .cbi-button-down{
+.cbi-rowstyle-2 .cbi-button-down {
     background-color: #FFF !important;
 }
 
@@ -768,7 +772,7 @@ form.inline + form.inline,
 
 .cbi-page-actions {
     border-top: 1px solid #eee;
-    padding-top: 2rem;
+    padding-top: 1rem;
     text-align: right;
 }
 
@@ -792,18 +796,23 @@ form.inline + form.inline,
     box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
 }
 
+td > .ifacebadge {
+    background-color: #F0F0F0;
+    font-size: 0.9rem;
+}
+
 .ifacebadge > img {
     float: right;
-    margin-left: 0.3rem;
+    margin: 0 0.3rem;
 }
 
 /*textarea*/
 
 .cbi-input-textarea {
     width: 100%;
-    min-height: 16rem;
-    padding: 1rem;
-    font-size: 0.9rem;
+    min-height: 14rem;
+    padding: 0.8rem;
+    font-size: 0.8rem;
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
     color: black;
 }
@@ -933,7 +942,26 @@ form.inline + form.inline,
 
 .cbi-value-field .cbi-input-checkbox,
 .cbi-value-field .cbi-input-radio {
-    margin-top: 0.3rem;
+    margin-top: 0.5rem;
+    height: 1rem;
+}
+
+.cbi-value-field > input + .cbi-value-description {
+    padding: 0;
+}
+
+.cbi-value-field > ul > li {
+    display: flex;
+}
+
+.cbi-value-field > ul > li > label {
+    margin-top: 0.5rem;
+}
+
+.cbi-value-field > ul > li .ifacebadge {
+    background-color: #eee;
+    margin-left: 0.4rem;
+    margin-top: -0.5rem;
 }
 
 .cbi-section-table-row > .cbi-value-field .cbi-input-select {
@@ -944,14 +972,19 @@ form.inline + form.inline,
     margin: 0.5rem;
 }
 
+.cbi-section-remove {
+    padding: 0.5rem;
+}
+
 div.cbi-value var, td.cbi-value-field var {
     font-style: italic;
     color: #0069D6;
 }
 
 small {
-    font-size: small;
+    font-size: 90%;
     white-space: normal;
+    line-height: 1.42857143;
 }
 
 .cbi-button-up,
@@ -959,7 +992,7 @@ small {
     display: inline-block;
     min-width: 0;
     padding: 0.2rem 0.3rem;
-    font-size: 1.3rem;
+    font-size: 1.2rem;
 }
 
 .cbi-optionals {
@@ -976,7 +1009,7 @@ small {
     -moz-border-radius: 3px;
     white-space: pre-wrap;
     word-wrap: break-word;
-    font-size: 1.5rem;
+    font-size: 1.4rem;
     color: #404040;
 }
 
@@ -998,7 +1031,7 @@ header > .container > .pull-right > * {
 
 .label {
     padding: 0.3rem 0.8rem;
-    font-size: 1rem;
+    font-size: 0.8rem;
     font-weight: bold;
     color: #ffffff !important;
     text-transform: uppercase;
@@ -1115,6 +1148,7 @@ header > .container > .pull-right > * {
 /* fix Services  Network Shares*/
 .node-services-samba > .main .cbi-tabcontainer:nth-child(3) .cbi-value-title {
     margin-bottom: 1rem;
+    width: auto;
 }
 
 .node-services-samba > .main .cbi-tabcontainer:nth-child(3) .cbi-value-field {
@@ -1137,7 +1171,7 @@ header > .container > .pull-right > * {
 }
 
 .node-system-packages > .main .cbi-tabmenu > li > a, .tabs > li > a {
-    padding: 0.5rem 1rem;
+    padding: 0.5rem 0.8rem;
 }
 
 .node-system-packages > .main .cbi-value > pre {
@@ -1168,25 +1202,28 @@ header > .container > .pull-right > * {
     box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
 }
 
+.node-system-flashops form.inline + form.inline {
+    margin-left: 0;
+}
+
 #cbi-firewall-redirect table *,
 #cbi-network-switch_vlan table *,
-#cbi-firewall-zone table *{
+#cbi-firewall-zone table * {
     font-size: small;
 }
 
 #cbi-firewall-redirect table input[type="text"],
 #cbi-network-switch_vlan table input[type="text"],
-#cbi-firewall-zone table input[type="text"]{
+#cbi-firewall-zone table input[type="text"] {
     width: 5rem;
 }
 
 #cbi-firewall-redirect table select,
 #cbi-network-switch_vlan table select,
-#cbi-firewall-zone table select{
+#cbi-firewall-zone table select {
     min-width: 3.5rem;
 }
 
-
 /* language fix */
 body.lang_pl.node-main-login .cbi-value-title {
     width: 12rem;
@@ -1194,17 +1231,16 @@ body.lang_pl.node-main-login .cbi-value-title {
 
 @media screen and (max-width: 1600px) {
     .main-left {
-        width: calc(0% + 15rem);
+        width: calc(0% + 13rem);
     }
 
     .main-right {
-        width: calc(100% - 15rem);
+        width: calc(100% - 13rem);
     }
 
     .cbi-button {
         padding: 0.3rem 0.6rem;
-        font-size: 1rem;
-        line-height: 1.5;
+        font-size: 0.8rem;
     }
 
     header > .container > .pull-right > * {
@@ -1262,23 +1298,23 @@ body.lang_pl.node-main-login .cbi-value-title {
     }
 
     .panel-title {
-        font-size: 1.3rem;
+        font-size: 1.1rem;
         padding-bottom: 1rem;
     }
 
     table {
-        font-size: 0.8rem !important;
+        font-size: 0.7rem !important;
         width: 100% !important;
     }
 
     .main > .main-left > .nav > li,
     .main > .main-left > .nav > li a,
     .main > .main-left > .nav > .slide > .menu {
-        font-size: 1.1rem;
+        font-size: 0.9rem;
     }
 
     .main > .main-left > .nav > .slide > .slide-menu > li > a {
-        font-size: 0.9rem;
+        font-size: 0.7rem;
     }
 }
 
@@ -1335,7 +1371,7 @@ body.lang_pl.node-main-login .cbi-value-title {
     }
 
     #diag-rc-output > pre {
-        font-size: 1.2rem;
+        font-size: 1rem;
     }
 
     .node-main-login > .main .cbi-value-title {
@@ -1344,6 +1380,10 @@ body.lang_pl.node-main-login .cbi-value-title {
 }
 
 @media screen and (max-width: 480px) {
+    body {
+        font-size: 1rem;
+    }
+
     fieldset {
         padding: 1rem;
         margin: 1rem 0 0 0;
@@ -1465,12 +1505,12 @@ body.lang_pl.node-main-login .cbi-value-title {
         -webkit-overflow-scrolling: touch;
     }
 
-    .node-status-iptables > .main div > .cbi-map > form input[type="submit"]{
+    .node-status-iptables > .main div > .cbi-map > form input[type="submit"] {
         width: 100% !important;
         margin: 0;
     }
 
-    .node-status-iptables > .main div > .cbi-map > form input[type="submit"] + input[type="submit"]{
+    .node-status-iptables > .main div > .cbi-map > form input[type="submit"] + input[type="submit"] {
         margin-top: 1rem;
     }
 }
@@ -1506,4 +1546,4 @@ body.lang_pl.node-main-login .cbi-value-title {
     .cbi-value-field .cbi-input-select {
         min-width: 25rem;
     }
-}
+}
\ No newline at end of file
index 03e5739..583d66d 100755 (executable)
@@ -95,6 +95,7 @@
                 ul.removeClass("active");
             });
         }
+        return false;
     });
 
     /**
         $(this).addClass("active");
         $(".main > .loading").fadeIn("fast");
         window.location = $($(this).find("a")[0]).attr("href");
-        return;
+        return false;
     });
 
     /**
         that.after("<span class='panel-title'>" + that.text() + "</span>");
     });
 
+    $(".cbi-section-table-titles, .cbi-section-table-descr, .cbi-section-descr").each(function () {
+        var that = $(this);
+        if (that.text().trim() == ""){
+            that.css("display", "none");
+        }
+    });
+
 
     $(".main-right").focus();
     $(".main-right").blur();
index 46e008d..0dc24aa 100755 (executable)
@@ -50,7 +50,7 @@
                        c._menu_selected = true
                end
        end
-    
+
     -- send as HTML5
        http.prepare_content("text/html")
 
 
                local childs = disp.node_childs(node)
                if #childs > 0 then
-        
+
             if level > 2 then
 %>
        <ul class="tabs">
-               <%  
+               <%
             end
 
                        local selected_node
@@ -93,7 +93,7 @@
                        </li>
                <%      end
                        end
-            
+
             if level > 2 then
                %>
        </ul>
 <head>
     <meta charset="utf-8">
     <title><%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
-    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
+    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/>
     <meta name="format-detection" content="telephone=no, email=no"/>
     <meta name="apple-mobile-web-app-capable" content="yes">
     <meta name="mobile-web-app-capable" content="yes">
     <meta name="theme-color" content="#0099CC">
     <meta name="msapplication-tap-highlight" content="no">
     <meta name="msapplication-TileColor" content="#0099CC">
+
+    <meta name="application-name" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
+    <meta name="apple-mobile-web-app-title" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
     <meta name="msapplication-TileImage" content="<%=media%>/logo.png"/>
+    <link rel="icon" href="<%=media%>/logo.png" sizes="144x144">
+    <link rel="apple-touch-icon-precomposed" href="<%=media%>/logo.png" sizes="144x144">
+
     <link rel="stylesheet" href="<%=media%>/css/style.css">
     <link rel="shortcut icon" href="<%=media%>/favicon.ico">
     <% if node and node.css then %>
index 4a6877f..c5ee58f 100644 (file)
@@ -925,9 +925,19 @@ div.cbi-tab-descr {
 .ifacebadge {
        background-color: #FFFFFF;
        border: 1px solid #CCCCCC;
-       padding: 2px;
+       padding: 1px 2px;
        margin-left: 2px;
        display: inline-block;
+       cursor: default;
+       white-space: nowrap;
+       font-size: 11px;
+       border-radius: 3px;
+}
+
+.ifacebadge img {
+       width: 16px;
+       height: 16px;
+       vertical-align: middle;
 }
 
 .ifacebadge-active {
index d43cc9c..505e64b 100644 (file)
        http.prepare_content("application/xhtml+xml")
 
        local function nodeurl(prefix, name, query)
-               local url = controller .. prefix .. name .. "/"
+               local u = url(prefix, name)
                if query then
-                       url = url .. http.build_querystring(query)
+                       u = u .. http.build_querystring(query)
                end
-               return pcdata(url)
+               return pcdata(u)
        end
 
-       local function subtree(prefix, node, level)
+       local function render_menu(prefix, node, level)
                if not level then
                        level = 1
                end
 
                local childs = disp.node_childs(node)
                if #childs > 0 then
-%>
-       <div class="tabmenu<%=level%>">
-       <ul class="tabmenu l<%=level%>">
-               <%
+                       write('<div class="tabmenu%d"><ul class="tabmenu l%d">' %{
+                               level, level
+                       })
+
                        local selected_node
                        local selected_name
                        local i, v
                                        selected_node = nnode
                                        selected_name = v
                                end
-               %>
-                       <li class="tabmenu-item-<%=v%><% if nnode._menu_selected or (node.leaf and v == leaf) then %> active<% end %>">
-                               <a href="<%=nodeurl(prefix, v, nnode.query)%>"><%=striptags(translate(nnode.title))%></a>
-                       </li>
-               <%
+
+                               write('<li class="tabmenu-item-%s %s"><a href="%s">%s</a></li>' %{
+                                       v, (nnode._menu_selected or (node.leaf and v == leaf)) and 'active' or '',
+                                       nodeurl(prefix, v, nnode.query),
+                                       striptags(translate(nnode.title))
+                               })
                        end
-               %>
-       </ul>
-       <br style="clear:both" />
-<%
+
+                       write('</ul><br style="clear:both" />')
+
                        if selected_node then
-                               subtree(prefix .. selected_name .. "/", selected_node, level + 1)
+                               render_menu(prefix .. "/" .. selected_name, selected_node, level + 1)
                        end
-%>
-       </div>
-<%
+
+                       write('</div>')
+               end
+       end
+
+       local function render_changes()
+               if tree.nodes[category] and tree.nodes[category].ucidata then
+                       local ucic = 0
+                       for i, j in pairs(require("luci.model.uci").cursor():changes()) do
+                               for k, l in pairs(j) do
+                                       for m, n in pairs(l) do
+                                               ucic = ucic + 1;
+                                       end
+                               end
+                       end
+
+                       write('<div id="savemenu">')
+
+                       if ucic > 0 then
+                               write('<a class="warning" href="%s?redir=%s">%s: %d</a>' %{
+                                       url(category, 'uci/changes'),
+                                       http.urlencode(http.formvalue('redir') or REQUEST_URI),
+                                       translate('Unsaved Changes'),
+                                       ucic
+                               })
+                       else
+                               write('<a href="#">%s: 0</a>' %{
+                                       translate('Unsaved Changes')
+                               })
+                       end
+
+                       write('</div>')
                end
        end
 -%>
        </ul>
 <% end %>
 
-<%
-if tree.nodes[category] and tree.nodes[category].ucidata then
-       local ucic = 0
-       for i, j in pairs(require("luci.model.uci").cursor():changes()) do
-               for k, l in pairs(j) do
-                       for m, n in pairs(l) do
-                               ucic = ucic + 1;
-                       end
-               end
-       end
--%>
-<div id="savemenu">
-       <% if ucic > 0 then %>
-               <a class="warning" href="<%=controller%>/<%=category%>/uci/changes/?redir=<%=luci.http.urlencode(luci.http.formvalue("redir") or REQUEST_URI)%>"><%:Unsaved Changes%>: <%=ucic%></a>
-       <%- else -%>
-               <a href="#"><%:Changes%>: 0</a>
-       <% end -%>
-</div><% end %>
+<% render_changes() %>
 
 <div class="clear"></div>
 </div>
 
 <div id="maincontainer">
        <div id="tabmenu">
-               <% if category then subtree("/" .. category .. "/", cattree) end %>
+               <% if category then render_menu(category, cattree) end %>
        </div>
 
        <div id="maincontent">