luci2: make sort / remove column in L.cbi.TableSection as narrow as possible
[project/luci2/ui.git] / luci2 / htdocs / luci2 / luci2.js
index ea64074..7ade584 100644 (file)
@@ -1109,6 +1109,35 @@ function LuCI2()
                        return this.set(conf, sid, opt, undefined);
                },
 
+               get_first: function(conf, type, opt)
+               {
+                       var sid = undefined;
+
+                       L.uci.sections(conf, type, function(s) {
+                               if (typeof(sid) != 'string')
+                                       sid = s['.name'];
+                       });
+
+                       return this.get(conf, sid, opt);
+               },
+
+               set_first: function(conf, type, opt, val)
+               {
+                       var sid = undefined;
+
+                       L.uci.sections(conf, type, function(s) {
+                               if (typeof(sid) != 'string')
+                                       sid = s['.name'];
+                       });
+
+                       return this.set(conf, sid, opt, val);
+               },
+
+               unset_first: function(conf, type, opt)
+               {
+                       return this.set_first(conf, type, opt, undefined);
+               },
+
                _reload: function()
                {
                        var pkgs = [ ];
@@ -2708,6 +2737,30 @@ function LuCI2()
                        return dev.getTrafficHistory();
                },
 
+               renderBadge: function()
+               {
+                       var badge = $('<span />')
+                               .addClass('badge')
+                               .text('%s: '.format(this.name()));
+
+                       var dev = this.getDevice();
+                       var subdevs = this.getSubdevices();
+
+                       if (subdevs.length)
+                               for (var j = 0; j < subdevs.length; j++)
+                                       badge.append($('<img />')
+                                               .attr('src', subdevs[j].icon())
+                                               .attr('title', '%s (%s)'.format(subdevs[j].description(), subdevs[j].name() || '?')));
+                       else if (dev)
+                               badge.append($('<img />')
+                                       .attr('src', dev.icon())
+                                       .attr('title', '%s (%s)'.format(dev.description(), dev.name() || '?')));
+                       else
+                               badge.append($('<em />').text(L.tr('(No devices attached)')));
+
+                       return badge;
+               },
+
                setDevices: function(devs)
                {
                        var dev = this.getPhysdev();
@@ -4558,8 +4611,7 @@ function LuCI2()
 
                'ipaddr': function()
                {
-                       if (validation.types['ip4addr'].apply(this) ||
-                               validation.types['ip6addr'].apply(this))
+                       if (L.parseIPv4(this) || L.parseIPv6(this))
                                return true;
 
                        validation.i18n('Must be a valid IP address');
@@ -4568,17 +4620,8 @@ function LuCI2()
 
                'ip4addr': function()
                {
-                       if (this.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(\/(\S+))?$/))
-                       {
-                               if ((RegExp.$1 >= 0) && (RegExp.$1 <= 255) &&
-                                   (RegExp.$2 >= 0) && (RegExp.$2 <= 255) &&
-                                   (RegExp.$3 >= 0) && (RegExp.$3 <= 255) &&
-                                   (RegExp.$4 >= 0) && (RegExp.$4 <= 255) &&
-                                   ((RegExp.$6.indexOf('.') < 0)
-                                     ? ((RegExp.$6 >= 0) && (RegExp.$6 <= 32))
-                                     : (validation.types['ip4addr'].apply(RegExp.$6))))
-                                       return true;
-                       }
+                       if (L.parseIPv4(this))
+                               return true;
 
                        validation.i18n('Must be a valid IPv4 address');
                        return false;
@@ -4586,62 +4629,74 @@ function LuCI2()
 
                'ip6addr': function()
                {
-                       if (this.match(/^([a-fA-F0-9:.]+)(\/(\d+))?$/))
-                       {
-                               if (!RegExp.$2 || ((RegExp.$3 >= 0) && (RegExp.$3 <= 128)))
-                               {
-                                       var addr = RegExp.$1;
+                       if (L.parseIPv6(this))
+                               return true;
 
-                                       if (addr == '::')
-                                       {
-                                               return true;
-                                       }
+                       validation.i18n('Must be a valid IPv6 address');
+                       return false;
+               },
 
-                                       if (addr.indexOf('.') > 0)
-                                       {
-                                               var off = addr.lastIndexOf(':');
+               'netmask4': function()
+               {
+                       if (L.isNetmask(L.parseIPv4(this)))
+                               return true;
 
-                                               if (!(off && validation.types['ip4addr'].apply(addr.substr(off+1))))
-                                               {
-                                                       validation.i18n('Must be a valid IPv6 address');
-                                                       return false;
-                                               }
+                       validation.i18n('Must be a valid IPv4 netmask');
+                       return false;
+               },
 
-                                               addr = addr.substr(0, off) + ':0:0';
-                                       }
+               'netmask6': function()
+               {
+                       if (L.isNetmask(L.parseIPv6(this)))
+                               return true;
 
-                                       if (addr.indexOf('::') >= 0)
-                                       {
-                                               var colons = 0;
-                                               var fill = '0';
+                       validation.i18n('Must be a valid IPv6 netmask6');
+                       return false;
+               },
 
-                                               for (var i = 1; i < (addr.length-1); i++)
-                                                       if (addr.charAt(i) == ':')
-                                                               colons++;
+               'cidr4': function()
+               {
+                       if (this.match(/^([0-9.]+)\/(\d{1,2})$/))
+                               if (RegExp.$2 <= 32 && L.parseIPv4(RegExp.$1))
+                                       return true;
 
-                                               if (colons > 7)
-                                               {
-                                                       validation.i18n('Must be a valid IPv6 address');
-                                                       return false;
-                                               }
+                       validation.i18n('Must be a valid IPv4 prefix');
+                       return false;
+               },
 
-                                               for (var i = 0; i < (7 - colons); i++)
-                                                       fill += ':0';
+               'cidr6': function()
+               {
+                       if (this.match(/^([a-fA-F0-9:.]+)\/(\d{1,3})$/))
+                               if (RegExp.$2 <= 128 && L.parseIPv6(RegExp.$1))
+                                       return true;
 
-                                               if (addr.match(/^(.*?)::(.*?)$/))
-                                                       addr = (RegExp.$1 ? RegExp.$1 + ':' : '') + fill +
-                                                                  (RegExp.$2 ? ':' + RegExp.$2 : '');
-                                       }
+                       validation.i18n('Must be a valid IPv6 prefix');
+                       return false;
+               },
 
-                                       if (addr.match(/^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$/) != null)
-                                               return true;
+               'ipmask4': function()
+               {
+                       if (this.match(/^([0-9.]+)\/([0-9.]+)$/))
+                       {
+                               var addr = RegExp.$1, mask = RegExp.$2;
+                               if (L.parseIPv4(addr) && L.isNetmask(L.parseIPv4(mask)))
+                                       return true;
+                       }
 
-                                       validation.i18n('Must be a valid IPv6 address');
-                                       return false;
-                               }
+                       validation.i18n('Must be a valid IPv4 address/netmask pair');
+                       return false;
+               },
+
+               'ipmask6': function()
+               {
+                       if (this.match(/^([a-fA-F0-9:.]+)\/([a-fA-F0-9:.]+)$/))
+                       {
+                               var addr = RegExp.$1, mask = RegExp.$2;
+                               if (L.parseIPv6(addr) && L.isNetmask(L.parseIPv6(mask)))
+                                       return true;
                        }
 
-                       validation.i18n('Must be a valid IPv6 address');
+                       validation.i18n('Must be a valid IPv6 address/netmask pair');
                        return false;
                },
 
@@ -5044,7 +5099,7 @@ function LuCI2()
                        if (typeof(a) != typeof(b))
                                return true;
 
-                       if (typeof(a) == 'object')
+                       if ($.isArray(a))
                        {
                                if (a.length != b.length)
                                        return true;
@@ -5055,6 +5110,18 @@ function LuCI2()
 
                                return false;
                        }
+                       else if ($.isPlainObject(a))
+                       {
+                               for (var k in a)
+                                       if (!(k in b))
+                                               return true;
+
+                               for (var k in b)
+                                       if (!(k in a) || a[k] !== b[k])
+                                               return true;
+
+                               return false;
+                       }
 
                        return (a != b);
                },
@@ -5922,7 +5989,7 @@ function LuCI2()
                        return $('<div />')
                                .addClass('form-control-static')
                                .attr('id', this.id(sid))
-                               .html(this.ucivalue(sid));
+                               .html(this.ucivalue(sid) || this.label('placeholder'));
                },
 
                formvalue: function(sid)
@@ -5981,30 +6048,16 @@ function LuCI2()
                        for (var i = 0; i < interfaces.length; i++)
                        {
                                var iface = interfaces[i];
-                               var badge = $('<span />')
-                                       .addClass('badge')
-                                       .text('%s: '.format(iface.name()));
-
-                               var dev = iface.getDevice();
-                               var subdevs = iface.getSubdevices();
-
-                               if (subdevs.length)
-                                       for (var j = 0; j < subdevs.length; j++)
-                                               badge.append(this._device_icon(subdevs[j]));
-                               else if (dev)
-                                       badge.append(this._device_icon(dev));
-                               else
-                                       badge.append($('<em />').text(L.tr('(No devices attached)')));
 
                                $('<li />')
                                        .append($('<label />')
                                                .addClass(itype + ' inline')
-                                               .append($('<input />')
+                                               .append(this.validator(sid, $('<input />')
                                                        .attr('name', itype + id)
                                                        .attr('type', itype)
                                                        .attr('value', iface.name())
-                                                       .prop('checked', !!check[iface.name()]))
-                                               .append(badge))
+                                                       .prop('checked', !!check[iface.name()]), true))
+                                               .append(iface.renderBadge()))
                                        .appendTo(ul);
                        }
 
@@ -6013,11 +6066,11 @@ function LuCI2()
                                $('<li />')
                                        .append($('<label />')
                                                .addClass(itype + ' inline text-muted')
-                                               .append($('<input />')
+                                               .append(this.validator(sid, $('<input />')
                                                        .attr('name', itype + id)
                                                        .attr('type', itype)
                                                        .attr('value', '')
-                                                       .prop('checked', $.isEmptyObject(check)))
+                                                       .prop('checked', $.isEmptyObject(check)), true))
                                                .append(L.tr('unspecified')))
                                        .appendTo(ul);
                        }
@@ -6429,6 +6482,11 @@ function LuCI2()
                        return true;
                },
 
+               sort: function(section1, section2)
+               {
+                       return 0;
+               },
+
                sections: function(cb)
                {
                        var s1 = L.uci.sections(this.map.uci_package);
@@ -6439,6 +6497,8 @@ function LuCI2()
                                        if (this.filter(s1[i]))
                                                s2.push(s1[i]);
 
+                       s2.sort(this.sort);
+
                        if (typeof(cb) == 'function')
                                for (var i = 0; i < s2.length; i++)
                                        cb.call(this, s2[i]);
@@ -6993,6 +7053,7 @@ function LuCI2()
                        if (this.options.addremove !== false || this.options.sortable)
                        {
                                row.append($('<td />')
+                                       .css('width', '1%')
                                        .addClass('text-right')
                                        .append($('<div />')
                                                .addClass('btn-group')