luci-base: form.js: handle SectionValue objects in GridSection modals
[project/luci.git] / modules / luci-base / htdocs / luci-static / resources / validation.js
index 79ae1d67070efa8a0e078099c4c36e19fad37b15..6dddf964fb93d21fb07518cdd52fcb6d7ffc4b50 100644 (file)
@@ -1,6 +1,11 @@
 'use strict';
+'require baseclass';
 
-var Validator = L.Class.extend({
+function bytelen(x) {
+       return new Blob([x]).size;
+}
+
+var Validator = baseclass.extend({
        __name__: 'Validation',
 
        __init__: function(field, type, optional, vfunc, validatorFactory) {
@@ -56,9 +61,15 @@ var Validator = L.Class.extend({
                        valid = this.vstack[0].apply(this, this.vstack[1]);
 
                if (valid !== true) {
-                       this.field.setAttribute('data-tooltip', _('Expecting: %s').format(this.error));
+                       var message = _('Expecting: %s').format(this.error);
+                       this.field.setAttribute('data-tooltip', message);
                        this.field.setAttribute('data-tooltip-style', 'error');
-                       this.field.dispatchEvent(new CustomEvent('validation-failure', { bubbles: true }));
+                       this.field.dispatchEvent(new CustomEvent('validation-failure', {
+                               bubbles: true,
+                               detail: {
+                                       message: message
+                               }
+                       }));
                        return false;
                }
 
@@ -69,7 +80,12 @@ var Validator = L.Class.extend({
                        this.assert(false, valid);
                        this.field.setAttribute('data-tooltip', valid);
                        this.field.setAttribute('data-tooltip-style', 'error');
-                       this.field.dispatchEvent(new CustomEvent('validation-failure', { bubbles: true }));
+                       this.field.dispatchEvent(new CustomEvent('validation-failure', {
+                               bubbles: true,
+                               detail: {
+                                       message: valid
+                               }
+                       }));
                        return false;
                }
 
@@ -81,7 +97,7 @@ var Validator = L.Class.extend({
 
 });
 
-var ValidatorFactory = L.Class.extend({
+var ValidatorFactory = baseclass.extend({
        __name__: 'ValidatorFactory',
 
        create: function(field, type, optional, vfunc) {
@@ -221,7 +237,7 @@ var ValidatorFactory = L.Class.extend({
 
        types: {
                integer: function() {
-                       return this.assert(this.factory.parseInteger(this.value) !== NaN, _('valid integer value'));
+                       return this.assert(!isNaN(this.factory.parseInteger(this.value)), _('valid integer value'));
                },
 
                uinteger: function() {
@@ -229,7 +245,7 @@ var ValidatorFactory = L.Class.extend({
                },
 
                float: function() {
-                       return this.assert(this.factory.parseDecimal(this.value) !== NaN, _('valid decimal value'));
+                       return this.assert(!isNaN(this.factory.parseDecimal(this.value)), _('valid decimal value'));
                },
 
                ufloat: function() {
@@ -267,18 +283,21 @@ var ValidatorFactory = L.Class.extend({
                                _('valid IPv6 prefix value (0-128)'));
                },
 
-               cidr: function() {
-                       return this.assert(this.apply('cidr4') || this.apply('cidr6'), _('valid IPv4 or IPv6 CIDR'));
+               cidr: function(negative) {
+                       return this.assert(this.apply('cidr4', null, [negative]) || this.apply('cidr6', null, [negative]),
+                               _('valid IPv4 or IPv6 CIDR'));
                },
 
-               cidr4: function() {
-                       var m = this.value.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})$/);
-                       return this.assert(m && this.factory.parseIPv4(m[1]) && this.apply('ip4prefix', m[2]), _('valid IPv4 CIDR'));
+               cidr4: function(negative) {
+                       var m = this.value.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(-)?(\d{1,2})$/);
+                       return this.assert(m && this.factory.parseIPv4(m[1]) && (negative || !m[2]) && this.apply('ip4prefix', m[3]),
+                               _('valid IPv4 CIDR'));
                },
 
-               cidr6: function() {
-                       var m = this.value.match(/^([0-9a-fA-F:.]+)\/(\d{1,3})$/);
-                       return this.assert(m && this.factory.parseIPv6(m[1]) && this.apply('ip6prefix', m[2]), _('valid IPv6 CIDR'));
+               cidr6: function(negative) {
+                       var m = this.value.match(/^([0-9a-fA-F:.]+)\/(-)?(\d{1,3})$/);
+                       return this.assert(m && this.factory.parseIPv6(m[1]) && (negative || !m[2]) && this.apply('ip6prefix', m[3]),
+                               _('valid IPv6 CIDR'));
                },
 
                ipnet4: function() {
@@ -299,18 +318,18 @@ var ValidatorFactory = L.Class.extend({
                        return this.assert(!(!v6 || v6[0] || v6[1] || v6[2] || v6[3]), _('valid IPv6 host id'));
                },
 
-               ipmask: function() {
-                       return this.assert(this.apply('ipmask4') || this.apply('ipmask6'),
+               ipmask: function(negative) {
+                       return this.assert(this.apply('ipmask4', null, [negative]) || this.apply('ipmask6', null, [negative]),
                                _('valid network in address/netmask notation'));
                },
 
-               ipmask4: function() {
-                       return this.assert(this.apply('cidr4') || this.apply('ipnet4') || this.apply('ip4addr'),
+               ipmask4: function(negative) {
+                       return this.assert(this.apply('cidr4', null, [negative]) || this.apply('ipnet4') || this.apply('ip4addr'),
                                _('valid IPv4 network'));
                },
 
-               ipmask6: function() {
-                       return this.assert(this.apply('cidr6') || this.apply('ipnet6') || this.apply('ip6addr'),
+               ipmask6: function(negative) {
+                       return this.assert(this.apply('cidr6', null, [negative]) || this.apply('ipnet6') || this.apply('ip6addr'),
                                _('valid IPv6 network'));
                },
 
@@ -330,13 +349,14 @@ var ValidatorFactory = L.Class.extend({
                        return this.assert(this.apply('port'), _('valid port or port range (port1-port2)'));
                },
 
-               macaddr: function() {
-                       return this.assert(this.value.match(/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/) != null,
-                               _('valid MAC address'));
+               macaddr: function(multicast) {
+                       var m = this.value.match(/^([a-fA-F0-9]{2}):([a-fA-F0-9]{2}:){4}[a-fA-F0-9]{2}$/);
+                       return this.assert(m != null && !(+m[1] & 1) == !multicast,
+                               multicast ? _('valid multicast MAC address') : _('valid MAC address'));
                },
 
                host: function(ipv4only) {
-                       return this.assert(this.apply('hostname') || this.apply(ipv4only == 1 ? 'ip4addr' : 'ipaddr'),
+                       return this.assert(this.apply('hostname') || this.apply(ipv4only == 1 ? 'ip4addr' : 'ipaddr', null, ['nomask']),
                                _('valid hostname or IP address'));
                },
 
@@ -353,8 +373,8 @@ var ValidatorFactory = L.Class.extend({
                },
 
                network: function() {
-                       return this.assert(this.apply('uciname') || this.apply('host'),
-                               _('valid UCI identifier, hostname or IP address'));
+                       return this.assert(this.apply('uciname') || this.apply('hostname') || this.apply('ip4addr') || this.apply('ip6addr'),
+                               _('valid UCI identifier, hostname or IP address range'));
                },
 
                hostport: function(ipv4only) {
@@ -420,24 +440,23 @@ var ValidatorFactory = L.Class.extend({
                },
 
                length: function(len) {
-                       var val = '' + this.value;
-                       return this.assert(val.length == +len,
+                       return this.assert(bytelen(this.value) == +len,
                                _('value with %d characters').format(len));
                },
 
                rangelength: function(min, max) {
-                       var val = '' + this.value;
-                       return this.assert((val.length >= +min) && (val.length <= +max),
+                       var len = bytelen(this.value);
+                       return this.assert((len >= +min) && (len <= +max),
                                _('value between %d and %d characters').format(min, max));
                },
 
                minlength: function(min) {
-                       return this.assert((''+this.value).length >= +min,
+                       return this.assert(bytelen(this.value) >= +min,
                                _('value with at least %d characters').format(min));
                },
 
                maxlength: function(max) {
-                       return this.assert((''+this.value).length <= +max,
+                       return this.assert(bytelen(this.value) <= +max,
                                _('value with at most %d characters').format(max));
                },
 
@@ -535,9 +554,9 @@ var ValidatorFactory = L.Class.extend({
 
                unique: function(subvalidator, subargs) {
                        var ctx = this,
-                           option = findParent(ctx.field, '[data-type][data-name]'),
+                           option = findParent(ctx.field, '[data-widget][data-name]'),
                            section = findParent(option, '.cbi-section'),
-                           query = '[data-type="%s"][data-name="%s"]'.format(option.getAttribute('data-type'), option.getAttribute('data-name')),
+                           query = '[data-widget="%s"][data-name="%s"]'.format(option.getAttribute('data-widget'), option.getAttribute('data-name')),
                            unique = true;
 
                        section.querySelectorAll(query).forEach(function(sibling) {
@@ -567,6 +586,18 @@ var ValidatorFactory = L.Class.extend({
 
                string: function() {
                        return true;
+               },
+
+               directory: function() {
+                       return true;
+               },
+
+               file: function() {
+                       return true;
+               },
+
+               device: function() {
+                       return true;
                }
        }
 });