diff --git a/src/index.html b/src/index.html index 44d81b4..1d5e8d6 100644 --- a/src/index.html +++ b/src/index.html @@ -459,14 +459,24 @@

-
+
- + data-ng-model="wizard.ip.v6Prefix" + data-ng-required="!state.ip.register" + data-ip-address data-ip-version="6" data-ip-type="subnet"> +
+
+ .error.required +
+
+ .v6Prefix.error.subnet +
+
@@ -481,13 +491,14 @@

+ data-ng-model="wizard.ip.v4[name]" + data-ng-required="!state.ip.register" + data-ip-address data-ip-version="4">
- .v4.error.required + .error.required
.v4.error.ipv4Address @@ -523,13 +534,15 @@

name="ipv4Lan" translate translate-attr-placeholder=".v4.placeholder" translate-values="{index: (state.wifi.devices | objectLength) + 1}" - data-ng-model="wizard.ip.v4.lan" data-ipv4-address - data-ng-required="wizard.ip.meshLan && !state.ip.generate"> + data-ng-model="wizard.ip.v4.lan" + data-ng-required="wizard.ip.meshLan && !state.ip.generate" + data-ip-address + data-ip-version="4">
- .v4.error.required + .error.required
.v4.error.ipv4Address @@ -568,7 +581,9 @@

placeholder="{{'ip.distribute.subnet.placeholder' | translate}}" data-ng-model="wizard.ip.v4ClientSubnet" data-ng-required="wizard.ip.distribute && !state.ip.register" - data-ng-pattern="ipv4Pattern+'\/(25|26|27|28)'"> + data-ip-address + data-ip-version="4" + data-ip-type="subnet">

.distribute.subnet.help

@@ -576,7 +591,7 @@

.error.required
-
+
.distribute.subnet.error.pattern
diff --git a/src/js/controllers/wizard.js b/src/js/controllers/wizard.js index d461f8b..2f7e5c3 100644 --- a/src/js/controllers/wizard.js +++ b/src/js/controllers/wizard.js @@ -263,22 +263,6 @@ module.exports = function(app) { }); }; - // helpers for validation - $scope.ipv4Pattern = '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' + - '\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' + - '\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' + - '\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; - - //I really hate v6 regex patterns >:( - - //http://home.deds.nl/~aeron/regex/ - //$scope.ipv6Pattern = '((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|ß\b)|){5}|([\\dA-F]{1,4}:){6})(((\[\dA-F]{1,4}((?!\\3)::|:\\b|$))|(?! \\2\\3)){2})'; - - //TODO more test and prefix addition - $scope.ipv6PrefixPattern = '((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}'; - - //http://regexlib.com/Search.aspx?k=ipv6&AspxAutoDetectCookieSupport=1 - $scope.hasError = function(field) { var form = $scope.wizardForm; return (form.$submitted || form[field].$dirty) && form[field].$invalid; diff --git a/src/js/directives/ipv4Address.js b/src/js/directives/ipv4Address.js index 4fda37a..3de9aa7 100644 --- a/src/js/directives/ipv4Address.js +++ b/src/js/directives/ipv4Address.js @@ -4,21 +4,32 @@ var ip = require('ip'); module.exports = function(app) { // check if the provided value is a valid ipv4 address - app.directive('ipv4Address', function() { + app.directive('ipAddress', function() { return { require: 'ngModel', link: function(scope, element, attributes, ngModel) { - ngModel.$validators.ipv4Address = function(modelValue) { + + var ipv4Pattern = '(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}' + + '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; + var ipv4SubnetPattern = ipv4Pattern + '\/(25|26|27|28)'; + + // there are a lot of regex for ipv6 + // http://regexlib.com/Search.aspx?k=ipv6 + // this one matches the expanded ipv6 pattern + var ipv6Pattern = '([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}'; + var ipv6SubnetPattern = ipv6Pattern + '\/(64|65)'; + + var ipv4AddressValidator = function(modelValue) { //validation for required should be done via ng-required directive //or the validation even fails if the field is not required - if (typeof modelValue == 'undefined' || modelValue === '') { + if (isEmpty(modelValue)) { return true; } // we run an additional regular expression against the model // because the 'ip' module does not catch all cases - var ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - if (!ipRegex.exec(modelValue)) { + if (!regexMatches(ipv4Pattern, modelValue)) { + console.log('ipv4 pattern does not match'); return false; } @@ -32,6 +43,99 @@ module.exports = function(app) { return true; }; + var ipv4SubnetValidator = function(modelValue) { + //validation for required should be done via ng-required directive + //or the validation even fails if the field is not required + if (isEmpty(modelValue)) { + return true; + } + return regexMatches(ipv4SubnetPattern, modelValue); + }; + + var ipv6AddressValidator = function(modelValue) { + if (isEmpty(modelValue)) { + return true; + } + modelValue = expandV6Address(modelValue); + return regexMatches(ipv6Pattern, modelValue); + }; + + var ipv6SubnetValidator = function(modelValue) { + if (isEmpty(modelValue)) { + return true; + } + + var splitted = modelValue.split('/'); + modelValue = expandV6Address(splitted[0]) + '/' + splitted[1]; + var valid = regexMatches(ipv6SubnetPattern, modelValue); + return valid; + }; + + //expands an ipv6 address + var expandV6Address = function(modelValue) { + var collapsedV6 = modelValue.split(':'); + modelValue = ''; + for (var i = 0; i < collapsedV6.length; i++) { + if (isEmpty(collapsedV6[i]) && isEmpty(collapsedV6[i + 1])) { + //if there are two empty array items in a row + //it means the pattern :: has to be replaced with :0 + for (var j = 8; j > collapsedV6.length - 2; j--) { + modelValue = modelValue + ':0'; + } + //skip next entry + i++; + } else { + modelValue = modelValue + ':' + collapsedV6[i]; + } + } + return modelValue.substr(1); + }; + + var regexMatches = function(pattern, modelValue) { + var ipRegex = new RegExp('^' + pattern + '$'); + if (ipRegex.exec(modelValue)) { + return true; + } + return false; + }; + + var isEmpty = function(modelValue) { + if (typeof modelValue == 'undefined' || modelValue === '') { + return true; + } + return false; + }; + + //add validators + if (typeof attributes.ipVersion == 'undefined' || + attributes.ipVersion == '4') { + if (typeof attributes.ipType == 'undefined') { + ngModel.$validators.ipv4Address = ipv4AddressValidator; + } else if (attributes.ipType == 'subnet') { + ngModel.$validators.ipv4Subnet = ipv4SubnetValidator; + } else { + if (console) { + console.error('ip type >' + attributes.ipType + + '< is not supported'); + } + } + } else if (attributes.ipVersion == '6') { + if (typeof attributes.ipType == 'undefined') { + ngModel.$validators.ipv6Address = ipv6AddressValidator; + } else if (attributes.ipType == 'subnet') { + ngModel.$validators.ipv6Subnet = ipv6SubnetValidator; + } else { + if (console) { + console.error('ip type >' + attributes.ipType + + '< is not supported'); + } + } + } else { + if (console) { + console.error('ip version >' + attributes.ipVersion + + '< is not supported'); + } + } } }; }); diff --git a/src/nls/locale-en.json b/src/nls/locale-en.json index 8dc8655..938708b 100644 --- a/src/nls/locale-en.json +++ b/src/nls/locale-en.json @@ -91,11 +91,11 @@ }, "v6Prefix": { "label": "IPv6 prefix", + "error.subnet": "This is not a valid IPv6 prefix.", "placeholder": "looks like 2001:bf7:c4ff:3300::/56" }, "v4": { "placeholder": "looks like 10.31.133.{{40+index}}", - "error.required": "This field is required.", "error.ipv4Address": "Not a valid IPv4 address.", "wifi": { "label": "Wifi ({{name}}) Mesh-IPv4"