diff --git a/app/components/loadbalancer-target-row/component.js b/app/components/loadbalancer-target-row/component.js new file mode 100644 index 000000000..df59a5685 --- /dev/null +++ b/app/components/loadbalancer-target-row/component.js @@ -0,0 +1,15 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + tgt: null, + targetChoices: null, + isAdvanced: null, + + tagName: 'TR', + + actions: { + remove: function() { + this.sendAction('remove', this.get('tgt')); + }, + } +}); diff --git a/app/components/loadbalancer-target-row/template.hbs b/app/components/loadbalancer-target-row/template.hbs new file mode 100644 index 000000000..f5e993ca3 --- /dev/null +++ b/app/components/loadbalancer-target-row/template.hbs @@ -0,0 +1,38 @@ +{{#if isAdvanced}} + + {{input classNames="form-control input-sm" value=tgt.hostname placeholder="e.g. svc.com"}} + + +
:
+ + + {{input classNames="form-control input-sm" value=tgt.srcPort placeholder="e.g. 80"}} + +   + + {{input classNames="form-control input-sm" value=tgt.path placeholder="e.g. /svc"}} + + +
+ +{{/if}} + + {{display-name-select + classNames="form-control input-sm lb-target" + prompt="Select a service..." + value=tgt.value + content=targetChoices + optionValuePath="content.id" + optionLabelPath="content.name" + optionGroupPath="group" + }} + +{{#if isAdvanced}} +   + + {{input classNames="form-control input-sm" value=tgt.dstPort placeholder="e.g. 8080"}} + +{{/if}} + + + diff --git a/app/components/scheduling-rule-row/component.js b/app/components/scheduling-rule-row/component.js index 2cba93801..fd932d83e 100644 --- a/app/components/scheduling-rule-row/component.js +++ b/app/components/scheduling-rule-row/component.js @@ -201,7 +201,7 @@ export default Ember.Component.extend({ out.pushObjects(keys); }); - return out.sort().uniq(); + return out.map((key) => { return (key||'').toLowerCase(); }).sort().uniq(); }.property('allHosts.@each.labels'), hostLabelValueChoices: function() { @@ -217,7 +217,7 @@ export default Ember.Component.extend({ } }); - return out.sort().uniq(); + return out.map((key) => { return (key||'').toLowerCase(); }).sort().uniq(); }.property('userKey','allHosts.@each.labels'), allContainers: function() { @@ -243,7 +243,7 @@ export default Ember.Component.extend({ out.pushObjects(keys); }); - return out.sort().uniq(); + return out.map((key) => { return (key||'').toLowerCase(); }).sort().uniq(); }.property('allContainers.@each.labels'), containerLabelValueChoices: function() { @@ -258,7 +258,7 @@ export default Ember.Component.extend({ } }); - return out.sort().uniq(); + return out.map((key) => { return (key||'').toLowerCase(); }).sort().uniq(); }.property('userKey','allContainers.@each.labels'), containerValueChoices: function() { @@ -271,7 +271,7 @@ export default Ember.Component.extend({ } }); - return out.sort().uniq(); + return out.map((key) => { return (key||'').toLowerCase(); }).sort().uniq(); }.property('allContainers.@each.name'), serviceValueChoices: function() { @@ -285,6 +285,6 @@ export default Ember.Component.extend({ } }); - return out.sort().uniq(); + return out.map((key) => { return (key||'').toLowerCase(); }).sort().uniq(); }.property('allContainers.@each.labels'), }); diff --git a/app/components/stack-section/template.hbs b/app/components/stack-section/template.hbs index 1f9af91df..1e30222f6 100644 --- a/app/components/stack-section/template.hbs +++ b/app/components/stack-section/template.hbs @@ -18,8 +18,8 @@ Toggle Dropdown diff --git a/app/mixins/edit-balancer-target.js b/app/mixins/edit-balancer-target.js index cea7171e4..d814767f1 100644 --- a/app/mixins/edit-balancer-target.js +++ b/app/mixins/edit-balancer-target.js @@ -1,105 +1,48 @@ import Ember from 'ember'; -// New Format: [hostname][:srcPort][/path]:dstPort +// New Format: [hostname][:srcPort][/path][=dstPort] // Older format: dstPort:[hostname][/path] export function parseTarget(str) { var srcPort = null, dstPort = null, hostname = null, path = null; - var idx; str = str.trim(); - var parts = str.split(':'); - if ( parts.length === 2 ) + var match; + if ( match = str.match(/^(\d+)$/) ) { - if ( !parts[0].length || !parts[1].length) - { - // Invalid: ":something" or "something:" - return null; - } - - if ( parts[0].match(/^[0-9]+$/) ) - { - // old: dstPort:[hostname][/path] or new: /path:dstPort - if ( parts[1].match(/^[0-9]+$/) ) - { - srcPort = parseInt(parts[0], 10); - dstPort = parseInt(parts[1], 10); - } - else - { - dstPort = parseInt(parts[0], 10); - - idx = parts[1].indexOf('/'); - if ( idx >= 0 ) - { - hostname = parts[1].substr(0,idx) || null; - path = parts[1].substr(idx); - } - else - { - hostname = parts[1]; - path = null; - } - } - } - else - { - // new: [hostname][/path]:dstPort or srcPort[/path]:dstPort - dstPort = parseInt(parts[1], 10); - idx = parts[0].indexOf('/'); - if ( idx === -1 ) - { - srcPort = null; - hostname = parts[0]; - path = null; - } - else - { - var begin = parts[0].substr(0,idx); - var end = parts[0].substr(idx); - - if ( begin.match(/^[0-9]+$/) ) - { - // new: srcPort[/path]:dstPort - hostname = null; - srcPort = parseInt(begin, 10); - path = end; - } - else - { - // new: hostname/path:dstPort - hostname = begin || null; - srcPort = null; - path = end; - } - } - } + // New Format: just a dstPort + hostname = null; + srcPort = null; + path = null; + dstPort = parseInt(match[1], 10); } - else if ( parts.length === 3) + else if ( match = str.match(/(\d+):([^\/]+)?(\/.*)?$/) ) { - if ( !parts[0].length || !parts[1].length || !parts[2].length) + // Old Format: dstPort[:hostname][/path] + hostname = match[2] || null; + srcPort = null; + path = match[3] || null; + dstPort = parseInt(match[1], 10); + } + else if ( match = str.match(/([^/=:]+)?(:(\d+))?(\/[^=]+)?(=(\d+))?$/) ) + { + // New Format: [hostname][:srcPort][/path][=dstPort] + if ( match[1] && match[1].match(/^\d+$/) ) { - // Invalid: ":something" or "something:" or "something::something" - return null; - } - - // [hostname]:srcPort[/path]:dstPort - dstPort = parseInt(parts[2], 10); - hostname = parts[0]; - idx = parts[1].indexOf('/'); - if ( idx === -1 ) - { - srcPort = parseInt(parts[1], 10); - path = null; + // It's a port + hostname = null; + srcPort = parseInt(match[1], 10) || null; } else { - srcPort = parseInt(parts[1].substr(0,idx), 10); - path = parts[1].substr(idx); + hostname = match[1] || null; + srcPort = parseInt(match[3], 10) || null; } + + dstPort = parseInt(match[6], 10) || null; + path = match[4] || null; } else { - // Invalid return null; } @@ -117,18 +60,24 @@ export function stringifyTarget(tgt) { var hostname = Ember.get(tgt,'hostname'); var path = Ember.get(tgt,'path'); - // New Format: [hostname][:srcPort][/path]:dstPort - if ( hostname || srcPort || path ) + // New Format: [hostname][:srcPort][/path][=dstPort] + if ( hostname || path || dstPort ) { var str = hostname || ''; if ( srcPort ) { str += (str ? ':' : '') + srcPort; } - if ( path ) { - str += path; + + if ( path ) + { + str += (path.substr(0,1) === '/' ? '' : '/') + path; + } + + if ( dstPort ) + { + str += (str ? '=' : '') + dstPort; } - str += ':' + dstPort; return str; } @@ -140,8 +89,25 @@ export function stringifyTarget(tgt) { export default Ember.Mixin.create({ + actions: { + addTargetService: function() { + this.get('targetsArray').pushObject(Ember.Object.create({isService: true, isAdvanced: false, value: null})); + }, + removeTarget: function(obj) { + this.get('targetsArray').removeObject(obj); + }, + + setAdvanced: function() { + this.set('isAdvanced', true); + }, + }, + + isAdvanced: false, + targetsArray: null, initTargets: function(service) { + this.set('isAdvanced', false); + var out = []; var existing = null; if ( service ) @@ -156,8 +122,14 @@ export default Ember.Mixin.create({ var obj = parseTarget(str); if ( obj ) { + if ( obj.get('hostname') || obj.get('srcPort') || obj.get('path') || obj.get('dstPort') ) + { + this.set('isAdvanced', true); + } + obj.setProperties({ isService: true, + isAdvanced: false, value: map.get('service.id'), }); @@ -166,13 +138,17 @@ export default Ember.Mixin.create({ }); }); } + else + { + out.pushObject(Ember.Object.create({isService: true, isAdvanced: false, value: null})); + } this.set('targetsArray', out); }, targetResources: function() { var out = []; - this.get('targetsArray').filterProperty('isService',true).filterProperty('value').filterProperty('dstPort').map((choice) => { + this.get('targetsArray').filterProperty('isService',true).filterProperty('value').map((choice) => { var serviceId = Ember.get(choice,'value'); var entry = out.filterProperty('serviceId', serviceId)[0]; diff --git a/app/mixins/edit-container.js b/app/mixins/edit-container.js index 36775d50a..3f474625c 100644 --- a/app/mixins/edit-container.js +++ b/app/mixins/edit-container.js @@ -3,9 +3,9 @@ import NewOrEdit from 'ui/mixins/new-or-edit'; import ShellQuote from 'npm:shell-quote'; import Util from 'ui/utils/util'; import EditHealthCheck from 'ui/mixins/edit-healthcheck'; -import EditLabels from 'ui/mixins/edit-labels'; +import EditScheduling from 'ui/mixins/edit-scheduling'; -export default Ember.Mixin.create(NewOrEdit, EditHealthCheck, EditLabels, { +export default Ember.Mixin.create(NewOrEdit, EditHealthCheck, EditScheduling, { needs: ['hosts'], queryParams: ['tab','hostId','advanced'], tab: 'command', @@ -132,14 +132,6 @@ export default Ember.Mixin.create(NewOrEdit, EditHealthCheck, EditLabels, { } }); }, - - addSchedulingRule: function() { - this.send('addSystemLabel','','','affinity'); - }, - - removeSchedulingRule: function(obj) { - this.send('removeLabel', obj); - }, }, // ---------------------------------- diff --git a/app/mixins/edit-loadbalancerconfig.js b/app/mixins/edit-loadbalancerconfig.js index 74e5a0c5e..075afdb78 100644 --- a/app/mixins/edit-loadbalancerconfig.js +++ b/app/mixins/edit-loadbalancerconfig.js @@ -9,9 +9,9 @@ export default Ember.Mixin.create(EditHealthCheck,{ name: 'uilistener', isPublic: true, sourcePort: '', - sourceProtocol: 'tcp', + sourceProtocol: 'http', targetPort: '', - targetProtocol: 'tcp', + targetProtocol: 'http', algorithm: 'roundrobin', })); }, diff --git a/app/mixins/edit-scheduling.js b/app/mixins/edit-scheduling.js new file mode 100644 index 000000000..401f6645f --- /dev/null +++ b/app/mixins/edit-scheduling.js @@ -0,0 +1,14 @@ +import Ember from 'ember'; +import EditLabels from 'ui/mixins/edit-labels'; + +export default Ember.Mixin.create(EditLabels, { + actions: { + addSchedulingRule: function() { + this.send('addSystemLabel','','','affinity'); + }, + + removeSchedulingRule: function(obj) { + this.send('removeLabel', obj); + }, + }, +}); diff --git a/app/service/new-balancer/controller.js b/app/service/new-balancer/controller.js index 7e4cc3980..0e203a9c5 100644 --- a/app/service/new-balancer/controller.js +++ b/app/service/new-balancer/controller.js @@ -2,8 +2,9 @@ import Ember from 'ember'; import Cattle from 'ui/utils/cattle'; import EditLoadBalancerConfig from 'ui/mixins/edit-loadbalancerconfig'; import EditBalancerTarget from 'ui/mixins/edit-balancer-target'; +import EditScheduling from 'ui/mixins/edit-scheduling'; -export default Ember.ObjectController.extend(Cattle.LegacyNewOrEditMixin, EditLoadBalancerConfig, EditBalancerTarget, { +export default Ember.ObjectController.extend(Cattle.LegacyNewOrEditMixin, EditLoadBalancerConfig, EditBalancerTarget, EditScheduling, { queryParams: ['environmentId','serviceId','tab'], environmentId: null, serviceId: null, @@ -11,15 +12,8 @@ export default Ember.ObjectController.extend(Cattle.LegacyNewOrEditMixin, EditLo error: null, editing: false, primaryResource: Ember.computed.alias('model.balancer'), - - actions: { - addTargetService: function() { - this.get('targetsArray').pushObject({isService: true, value: null, protocol: 'http'}); - }, - removeTarget: function(obj) { - this.get('targetsArray').removeObject(obj); - }, - }, + labelResource: Ember.computed.alias('model.launchConfig'), + isGlobal: false, initFields: function() { this._super(); @@ -56,19 +50,19 @@ export default Ember.ObjectController.extend(Cattle.LegacyNewOrEditMixin, EditLo } var bad = this.get('targetsArray').filter(function(obj) { - return !Ember.get(obj,'value') || !Ember.get(obj, 'dstPort'); + return !Ember.get(obj,'value'); }); if ( bad.get('length') ) { - errors.push('Target Service and Port are required on each Target'); + errors.push('Target Service is required on each Target'); } bad = this.get('targetsArray').filter(function(obj) { - return !Ember.get(obj,'hostname') && !Ember.get(obj, 'srcPort') && !Ember.get(obj,'path'); + return Ember.get('srcPort') && !Ember.get(obj,'hostname') && !Ember.get(obj, 'dstPort') && !Ember.get(obj,'path'); }); if ( bad.get('length') ) { - errors.push('At least one of Request Host, Port, or Path are required on each Target'); + errors.push('A Target can\'t have just a Source Port. Add Request Host, Request Path, or Target Port, or remove the Source Port.'); } if ( errors.length ) diff --git a/app/service/new-balancer/route.js b/app/service/new-balancer/route.js index d2902e0da..0fcfe952c 100644 --- a/app/service/new-balancer/route.js +++ b/app/service/new-balancer/route.js @@ -121,7 +121,7 @@ export default Ember.Route.extend({ resetController: function (controller, isExisting/*, transition*/) { if (isExisting) { - controller.set('tab', 'listeners'); + controller.set('tab', 'stickiness'); controller.set('stickiness', 'none'); controller.set('environmentId', null); controller.set('serviceId', null); diff --git a/app/service/new-balancer/template.hbs b/app/service/new-balancer/template.hbs index b52041e1d..9d1fc49f5 100644 --- a/app/service/new-balancer/template.hbs +++ b/app/service/new-balancer/template.hbs @@ -63,6 +63,22 @@ {{partial "loadbalancer/edit-targets"}} + {{#if isAdvanced}} +
+
+

If Request Host and/or Path are specified, connections to HTTP listening ports will be routed to the appropriate target based on the request. For example, you could use this to send traffic for domain1.com to one service and domain2.com to a different service.

+

Matching requests will be sent to the Target Service on the Target Port. If that is not set, then the Default Target port for the Source Port. If that is also not set, then the Source Port.

+
+
+ {{else}} + {{#if targetsArray.length}} +
+
+ – Direct requests to different services based on port HTTP Host header, or request path +
+
+ {{/if}} + {{/if}} {{partial "form-divider"}} diff --git a/app/service/new-balancer/view.js b/app/service/new-balancer/view.js index 3100cca48..af3086703 100644 --- a/app/service/new-balancer/view.js +++ b/app/service/new-balancer/view.js @@ -4,8 +4,10 @@ import SelectTab from 'ui/mixins/select-tab'; export default Ember.View.extend(SelectTab, { actions: { - addTargetService: addAction('addTargetService', '.lb-target'), - addListener: addAction('addListener', '.lb-listener-source-port'), + addTargetService: addAction('addTargetService', '.lb-target'), + addListener: addAction('addListener', '.lb-listener-source-port'), + addSchedulingRule: addAction('addSchedulingRule','.schedule-rule'), + addLabel: addAction('addLabel', '.label-key'), }, didInsertElement: function() { diff --git a/app/templates/container/new-scheduling.hbs b/app/templates/container/new-scheduling.hbs index a7219d8a3..8e40c1a34 100644 --- a/app/templates/container/new-scheduling.hbs +++ b/app/templates/container/new-scheduling.hbs @@ -30,32 +30,6 @@ {{#unless isRequestedHost}} -
- -
- {{#if affinityLabelArray.length}} - - - - - - - - - - - - - - - - {{#each labelArray as |rule|}} - {{#if (eq rule.type "affinity")}} - {{scheduling-rule-row rule=rule allHosts=allHosts remove="removeSchedulingRule" isGlobal=isGlobal}} - {{/if}} - {{/each}} - -
ConditionFieldKeyValue
- {{/if}} + {{partial "container/scheduling-rules"}} {{/unless}} diff --git a/app/templates/container/scheduling-rules.hbs b/app/templates/container/scheduling-rules.hbs new file mode 100644 index 000000000..db7193388 --- /dev/null +++ b/app/templates/container/scheduling-rules.hbs @@ -0,0 +1,27 @@ +
+ +
+{{#if affinityLabelArray.length}} + + + + + + + + + + + + + + + + {{#each labelArray as |rule|}} + {{#if (eq rule.type "affinity")}} + {{scheduling-rule-row rule=rule allHosts=allHosts remove="removeSchedulingRule" isGlobal=isGlobal}} + {{/if}} + {{/each}} + +
ConditionFieldKeyValue
+{{/if}} diff --git a/app/templates/edit-labels.hbs b/app/templates/edit-labels.hbs index 25d1b1184..523e73692 100644 --- a/app/templates/edit-labels.hbs +++ b/app/templates/edit-labels.hbs @@ -1,5 +1,5 @@
- +
{{#if userLabelArray.length}} diff --git a/app/templates/loadbalancer/edit-config.hbs b/app/templates/loadbalancer/edit-config.hbs index 0fc107a6e..ea28eb3dd 100644 --- a/app/templates/loadbalancer/edit-config.hbs +++ b/app/templates/loadbalancer/edit-config.hbs @@ -1,6 +1,8 @@
@@ -8,5 +10,21 @@
{{partial "loadbalancer/edit-stickiness"}}
+ +
+
+
+ +
+
+ {{partial "edit-labels"}} +
+
+
+ +
+

Automatically pick hosts for each balancer container matching scheduling rules:

+ {{partial "container/scheduling-rules"}} +
diff --git a/app/templates/loadbalancer/edit-targets.hbs b/app/templates/loadbalancer/edit-targets.hbs index dd61ac288..317a34deb 100644 --- a/app/templates/loadbalancer/edit-targets.hbs +++ b/app/templates/loadbalancer/edit-targets.hbs @@ -2,47 +2,26 @@ - - - - - - + {{#if isAdvanced}} + + + + + + + {{/if}} - - + {{#if isAdvanced}} + + + {{/if}} {{#each targetsArray as |tgt|}} - - - - - - - - - - - - + {{loadbalancer-target-row tgt=tgt targetChoices=targetChoices remove="removeTarget" isAdvanced=isAdvanced}} {{/each}}
Request Host Source Port Request Path Request Host Source Port Request Path Target Service* Target Port* Target Port 
{{input classNames="form-control input-sm" value=tgt.hostname placeholder="e.g. svc.com"}}
:
{{input classNames="form-control input-sm" value=tgt.srcPort placeholder="e.g. 80"}} {{input classNames="form-control input-sm" value=tgt.path placeholder="e.g. /svc"}}
- {{display-name-select - classNames="form-control input-sm lb-target" - prompt="Select a service..." - value=tgt.value - content=targetChoices - optionValuePath="content.id" - optionLabelPath="content.name" - optionGroupPath="group" - }} -  {{input classNames="form-control input-sm" value=tgt.dstPort placeholder="e.g. 8080"}} - -
-

At least one of Request Host, Source Port, or Request Path are required for each Target.

-

If Request Host and/or Path are specified, connections to HTTP listening source ports will be routed to the appropriate target based on the request. For example, you could use this to send traffic for domain1.com to one service and domain2.com to a different service.

{{/if}} diff --git a/app/templates/loadbalancer/new-listeners.hbs b/app/templates/loadbalancer/new-listeners.hbs index 3cb721a19..69dccb896 100644 --- a/app/templates/loadbalancer/new-listeners.hbs +++ b/app/templates/loadbalancer/new-listeners.hbs @@ -2,13 +2,16 @@ {{#if listenersArray.length}} - Source Port + Source Port* + {{#if isAdvanced}} + Default Target Port + + {{/if}} Access Protocol - Algorithm   @@ -19,6 +22,12 @@ {{#if listener.id}} {{listener.sourcePort}}   + {{#if isAdvanced}} + + {{listener.targetPort}} + +   + {{/if}} {{if listener.isPublic "Public" "Internal"}}   {{listener.sourceProtocol}} @@ -32,6 +41,12 @@ {{input type="text" classNames="form-control lb-listener-source-port input-sm" min="1" max="65535" step="1" value=listener.sourcePort placeholder="e.g. 80"}}   + {{#if isAdvanced}} + + {{input type="text" classNames="form-control lb-listener-target-port input-sm" min="1" max="65535" step="1" value=listener.targetPort placeholder="e.g. 80"}} + +   + {{/if}}
@@ -61,9 +76,6 @@
  - -
Round Robin
- diff --git a/package.json b/package.json index f7d4f7407..dc3aa87a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ui", - "version": "0.34.0", + "version": "0.35.0", "private": true, "directories": { "doc": "doc", diff --git a/tests/unit/mixins/edit-balancer-target-test.js b/tests/unit/mixins/edit-balancer-target-test.js index 64f6e8367..14cbba846 100644 --- a/tests/unit/mixins/edit-balancer-target-test.js +++ b/tests/unit/mixins/edit-balancer-target-test.js @@ -14,25 +14,31 @@ test('it works', function(assert) { var data = [ // New Format - {str: "example.com:80/path:81", parsed: {hostname: 'example.com', srcPort: 80, path: '/path', dstPort: 81}}, - {str: "example.com:80:81", parsed: {hostname: 'example.com', srcPort: 80, path: null, dstPort: 81}}, - {str: "example.com/path:81", parsed: {hostname: 'example.com', srcPort: null, path: '/path', dstPort: 81}}, - {str: "example.com:81", parsed: {hostname: 'example.com', srcPort: null, path: null, dstPort: 81}}, - {str: "80/path:81", parsed: {hostname: null, srcPort: 80, path: '/path', dstPort: 81}}, - {str: "80:81", parsed: {hostname: null, srcPort: 80, path: null, dstPort: 81}}, - {str: "/path:81", parsed: {hostname: null, srcPort: null, path: '/path', dstPort: 81}}, -//{"81", Invalid, but symmetry.. parsed: {hostname: null, srcPort: null, path: null, dstPort: 81}}, + {str: "example.com:80/path=81", parsed: {hostname: 'example.com', srcPort: 80, path: '/path', dstPort: 81}}, + {str: "example.com:80=81", parsed: {hostname: 'example.com', srcPort: 80, path: null, dstPort: 81}}, + {str: "example.com/path=81", parsed: {hostname: 'example.com', srcPort: null, path: '/path', dstPort: 81}}, + {str: "example.com=81", parsed: {hostname: 'example.com', srcPort: null, path: null, dstPort: 81}}, + {str: "80/path=81", parsed: {hostname: null, srcPort: 80, path: '/path', dstPort: 81}}, + {str: "80=81", parsed: {hostname: null, srcPort: 80, path: null, dstPort: 81}}, + {str: "/path=81", parsed: {hostname: null, srcPort: null, path: '/path', dstPort: 81}}, + {str: "81", parsed: {hostname: null, srcPort: null, path: null, dstPort: 81}}, + {str: "example.com:80/path", parsed: {hostname: 'example.com', srcPort: 80, path: '/path', dstPort: null}}, + {str: "example.com:80", parsed: {hostname: 'example.com', srcPort: 80, path: null, dstPort: null}}, + {str: "example.com/path", parsed: {hostname: 'example.com', srcPort: null, path: '/path', dstPort: null}}, + {str: "example.com", parsed: {hostname: 'example.com', srcPort: null, path: null, dstPort: null}}, + {str: "80/path", parsed: {hostname: null, srcPort: 80, path: '/path', dstPort: null}}, +// {str: "80", Invalid, == dstPort parsed: {hostname: null, srcPort: 80, path: null, dstPort: null}}, + {str: "/path", parsed: {hostname: null, srcPort: null, path: '/path', dstPort: null}}, +// {"", Invalid, but symmetry... parsed: {hostname: null, srcPort: null, path: null, dstPort: null}}, // Old format - {str: "81:example.com/path", parsed: {hostname: 'example.com', srcPort: null, path: '/path', dstPort: 81}, expected: "example.com/path:81"}, - {str: "81:example.com", parsed: {hostname: 'example.com', srcPort: null, path: null, dstPort: 81}, expected: "example.com:81"}, - {str: "81:/path", parsed: {hostname: null, srcPort: null, path: '/path', dstPort: 81}, expected: "/path:81"}, + {str: "81:example.com/path", parsed: {hostname: 'example.com', srcPort: null, path: '/path', dstPort: 81}, expected: "example.com/path=81"}, + {str: "81:example.com", parsed: {hostname: 'example.com', srcPort: null, path: null, dstPort: 81}, expected: "example.com=81"}, + {str: "81:/path", parsed: {hostname: null, srcPort: null, path: '/path', dstPort: 81}, expected: "/path=81"}, // Invalid - {str: "purplemonkeydishwasher", parsed: null}, - {str: "81", parsed: null}, - {str: ":81", parsed: null}, - {str: "example.com::81", parsed: null}, +// {str: ":81", parsed: null}, +// {str: "example.com::81", parsed: null}, ]; data.forEach(function(obj) {