From 1c017bbb6989722afb5ca44bf6a78bd010005480 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Thu, 7 Apr 2016 11:48:35 -0700 Subject: [PATCH 1/4] host:ip parser, fix SSL ports with bound IP (rancher/rancher#4314) --- .../form-balancer-listeners/component.js | 13 +-- app/components/form-ports/component.js | 4 +- app/models/loadbalancerservice.js | 6 +- app/utils/parse-port.js | 82 ++++++++++++++++++- tests/unit/utils/parse-port-test.js | 50 +++++++++-- 5 files changed, 135 insertions(+), 20 deletions(-) diff --git a/app/components/form-balancer-listeners/component.js b/app/components/form-balancer-listeners/component.js index b9d1cffb0..db4a1e957 100644 --- a/app/components/form-balancer-listeners/component.js +++ b/app/components/form-balancer-listeners/component.js @@ -1,7 +1,7 @@ import Ember from 'ember'; import C from 'ui/utils/constants'; import ManageLabels from 'ui/mixins/manage-labels'; -import { parsePort } from 'ui/utils/parse-port'; +import { parsePortSpec, parseIpPort } from 'ui/utils/parse-port'; export default Ember.Component.extend(ManageLabels, { initialPorts: null, @@ -46,7 +46,7 @@ export default Ember.Component.extend(ManageLabels, { var out = []; function add(isPublic, str) { - var obj = parsePort(str, 'http'); + var obj = parsePortSpec(str, 'http'); obj.setProperties({ isPublic: isPublic, ssl: sslPorts.indexOf(obj.get('hostPort')) >= 0, @@ -73,9 +73,12 @@ export default Ember.Component.extend(ManageLabels, { }.observes('listenersArray.[]'), sslChanged: function() { - var sslPorts = this.get('listenersArray'). - filterBy('host'). - filterBy('ssl',true).map((listener) => { return listener.get('host');}); + var sslPorts = this.get('listenersArray') + .filterBy('ssl',true) + .map((listener) => { return parseIpPort(listener.get('host'),'http'); }) + .filterBy('port') + .map((obj) => { return obj.port; }) + .sort().uniq(); if ( sslPorts.get('length') ) { diff --git a/app/components/form-ports/component.js b/app/components/form-ports/component.js index 8e1a22d98..b17b2e1a1 100644 --- a/app/components/form-ports/component.js +++ b/app/components/form-ports/component.js @@ -1,5 +1,5 @@ import Ember from 'ember'; -import { parsePort } from 'ui/utils/parse-port'; +import { parsePortSpec } from 'ui/utils/parse-port'; const protocolOptions = [ {label: 'TCP', value: 'tcp'}, @@ -54,7 +54,7 @@ export default Ember.Component.extend({ else if ( typeof value === 'string' ) { // Strings, from clone - var parsed = parsePort(value); + var parsed = parsePortSpec(value); out.push({ existing: false, public: parsed.host, diff --git a/app/models/loadbalancerservice.js b/app/models/loadbalancerservice.js index d190cc855..83091061a 100644 --- a/app/models/loadbalancerservice.js +++ b/app/models/loadbalancerservice.js @@ -2,17 +2,17 @@ import Service from 'ui/models/service'; import Ember from 'ember'; import C from 'ui/utils/constants'; import Util from 'ui/utils/util'; -import { parsePort } from 'ui/utils/parse-port'; +import { parsePortSpec } from 'ui/utils/parse-port'; const esc = Ember.Handlebars.Utils.escapeExpression; function portToStr(spec) { - var parts = parsePort(spec); + var parts = parsePortSpec(spec); return parts.host + (parts.protocol === 'http' ? '' : '/' + parts.protocol); } function specToPort(spec) { - var parts = parsePort(spec); + var parts = parsePortSpec(spec); return parts.hostPort; } diff --git a/app/utils/parse-port.js b/app/utils/parse-port.js index 8d86988ff..f5b0b033f 100644 --- a/app/utils/parse-port.js +++ b/app/utils/parse-port.js @@ -4,7 +4,7 @@ import Ember from 'ember'; // hostIp::containerPort // hostPort:containerPort // containerPort -export function parsePort(str, defaultProtocol='http') { +export function parsePortSpec(str, defaultProtocol='http') { str = str.trim(); var match, parts, hostIp = '', hostPort, containerPort, protocol; @@ -59,7 +59,7 @@ export function parsePort(str, defaultProtocol='http') { }); } -export function stringifyPort(port, defaultProtocol='http') { +export function stringifyPortSpec(port, defaultProtocol='http') { var hostStr = Ember.get(port,'host')||''; var match, hostIp, hostPort; if ( match = hostStr.match(/^((.*):)?([^:]+)$/) ) @@ -92,7 +92,81 @@ export function stringifyPort(port, defaultProtocol='http') { return out; } +// port +// 1.2.3.4 +// 1.2.3.4:port +// long:ip:v6::str +// [long:ip:v6::str] +// [long:ip:v6::str]:port +export function parseIpPort(str) { + str = str.trim(); + let colons = str.replace(/[^:]/g,'').length; + let index; + + // IPv6, IPv6+port + index = str.indexOf(']'); + if ( colons > 1 ) + { + let index = str.indexOf(']'); + if ( index > 0 ) + { + let ip = str.substr(0,index+1); + let port = null; + if ( str.substr(index+1,1) === ':' ) { + port = portToInt(str.substr(index+2)); + } + + return ret(ip,port); + } + else + { + return ret('['+str+']',null); + } + } + + // IPv4+port + index = str.indexOf(':'); + if ( index >= 0 ) + { + return ret(str.substr(0,index), str.substr(index+1)); + } + + // IPv4 + if ( str.match(/[^\d]/) ) + { + return ret(str,null); + } + + // Port + let port = portToInt(str); + if ( port ) + { + return ret(null,port); + } + + return null; + + function ret(ip,port) { + return { + ip: ip || null, + port: portToInt(port) + }; + } +} + +export function portToInt(str) { + str = (str+'').trim(); + if ( str.match(/^\d+$/) ) + { + return parseInt(str,10) || null; + } + + return null; +} + export default { - parsePort: parsePort, - stringifyPort: stringifyPort, + parsePortSpec: parsePortSpec, + stringifyPortSpec: stringifyPortSpec, + parseIpPort: parseIpPort, + portToInt: portToInt }; diff --git a/tests/unit/utils/parse-port-test.js b/tests/unit/utils/parse-port-test.js index 7ab71b04b..019ec4aa1 100644 --- a/tests/unit/utils/parse-port-test.js +++ b/tests/unit/utils/parse-port-test.js @@ -1,5 +1,5 @@ import Ember from 'ember'; -import { parsePort, stringifyPort } from 'ui/utils/parse-port'; +import { parsePortSpec, stringifyPortSpec, parseIpPort } from 'ui/utils/parse-port'; import { module, test } from 'qunit'; module('Unit | Utils | parse-port'); @@ -21,28 +21,66 @@ var data = [ data.forEach(function(obj) { var input = obj.str; - var actual = parsePort(input); + var actual = parsePortSpec(input); if ( obj.parsed ) { - test('it can parse: ' + obj.str, function(assert) { + test('it can parse spec: ' + obj.str, function(assert) { var expected = obj.parsed; Object.keys(expected).forEach((key) => { assert.strictEqual(Ember.get(actual,key), Ember.get(expected, key), key + ' parses correctly'); }); }); - test('it can stringify: ' + obj.str, function(assert) { + test('it can stringify spec: ' + obj.str, function(assert) { var input = obj.parsed; var expected = obj.expected || obj.str; - var actual = stringifyPort(input); + var actual = stringifyPortSpec(input); assert.strictEqual(actual, expected, 'Objects are stringified correctly'); }); } else { - test("it can't parse: " + obj.str, function(assert) { + test("it can't parse spec: " + obj.str, function(assert) { assert.strictEqual(actual, null, 'Invalid data is not parseable'); }); } }); + +data = [ + {str: '', parsed: null}, + {str: '80', parsed: {ip: null, port: 80 }}, + {str: 'asdf', parsed: {ip: 'asdf', port: null}}, + {str: '1.2.3.4', parsed: {ip: '1.2.3.4', port: null}}, + {str: '1.2.3.4:80', parsed: {ip: '1.2.3.4', port: 80 }}, + {str: '1.2.3.4:12ab', parsed: {ip: '1.2.3.4', port: null}}, + {str: 'asdf:12ab', parsed: {ip: 'asdf', port: null}}, + {str: '80asdf', parsed: {ip: '80asdf', port: null}}, + {str: '12:34:56::78', parsed: {ip: '[12:34:56::78]', port: null}}, + {str: '[12:34:56::78]', parsed: {ip: '[12:34:56::78]', port: null}}, + {str: '[12:34:56::78]:80', parsed: {ip: '[12:34:56::78]', port: 80 }}, + {str: '[12:34:56::78]:asdf', parsed: {ip: '[12:34:56::78]', port: null}}, + {str: '[12:34:56::78]:90:ab', parsed: {ip: '[12:34:56::78]', port: null}}, + {str: '2001:0db8:85a3:0000:0000:8a2e:0370:7334', parsed: {ip: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', port: null}}, + {str: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80', parsed: {ip: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', port: 80}}, +]; + +data.forEach(function(obj) { + var input = obj.str; + var actual = parseIpPort(input); + + test('it can parse: ' + obj.str, function(assert) { + var expected = obj.parsed; + + if ( expected === null ) + { + assert.strictEqual(actual, null, input + ' cannot be parsed'); + } + else + { + Object.keys(expected).forEach((key) => { + assert.strictEqual(Ember.get(actual,key), Ember.get(expected, key), key + ' parses correctly'); + }); + } + }); +}); From a1b37476b95eea2a1f510c8a3550211cf6bbe741 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Thu, 7 Apr 2016 12:43:55 -0700 Subject: [PATCH 2/4] Option to allow multiline values in form-key-value (rancher/rancher#4317) --- app/components/form-key-value/component.js | 1 + app/components/form-key-value/template.hbs | 14 +++++++++----- app/components/textarea-autogrow/component.js | 15 ++++++++++----- app/styles/layout/_layout.scss | 14 +++++++++++++- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/app/components/form-key-value/component.js b/app/components/form-key-value/component.js index 0ad68b94e..32b4c7ebd 100644 --- a/app/components/form-key-value/component.js +++ b/app/components/form-key-value/component.js @@ -63,6 +63,7 @@ export default Ember.Component.extend({ valuePlaceholder: 'Value', allowEmptyValue: false, addInitialEmptyRow: false, + allowMultilineValue: true, ary: null, asMap: null, diff --git a/app/components/form-key-value/template.hbs b/app/components/form-key-value/template.hbs index ce40c5000..07fc8cd56 100644 --- a/app/components/form-key-value/template.hbs +++ b/app/components/form-key-value/template.hbs @@ -10,19 +10,23 @@ {{#each ary as |row|}} - + {{input-paste pasted="pastedLabels" class="form-control input-sm key" type="text" value=row.key placeholder=keyPlaceholder}} - +

=

- - {{input class="form-control input-sm value" type="text" value=row.value placeholder=valuePlaceholder}} + + {{#if allowMultilineValue}} + {{textarea-autogrow class="form-control input-sm value" value=row.value placeholder=valuePlaceholder}} + {{else}} + {{input class="form-control input-sm value" type="text" value=row.value placeholder=valuePlaceholder}} + {{/if}} - + diff --git a/app/components/textarea-autogrow/component.js b/app/components/textarea-autogrow/component.js index c9f871644..2bbfcbd5b 100644 --- a/app/components/textarea-autogrow/component.js +++ b/app/components/textarea-autogrow/component.js @@ -2,14 +2,15 @@ import Ember from 'ember'; import { isGecko } from 'ui/utils/platform'; export default Ember.TextArea.extend({ - tagName: 'textarea', text: null, - classNames: [], - paddingAndBorder: null, - minHeight: 43, + minHeight: 0, maxHeight: 200, + tagName: 'textarea', + classNames: ['no-resize'], + didInsertElement() { + this.set('minHeight', ( this.get('isSmall') ? 31 : 43)); this.autoSize(); this.$().on('paste', () => { @@ -21,6 +22,10 @@ export default Ember.TextArea.extend({ this.autoSize(); }, + isSmall: function() { + return this.$().hasClass('input-sm'); + }.property(), + autoSize() { let el = this.element; let $el = $(el); @@ -29,7 +34,7 @@ export default Ember.TextArea.extend({ $el.css('height', '1px'); let border = parseInt($el.css('borderTopWidth'),10)||0 + parseInt($el.css('borderBottomWidth'),10)||0; - let magic = ( isGecko ? 1 : 2); // Sigh, but it's wrong without magic fudge + let magic = (this.get('isSmall') ? -2 : 0) + ( isGecko ? 1 : 2); // Sigh, but it's wrong without magic fudge let neu = Math.max(this.get('minHeight'), Math.min(el.scrollHeight + border + magic, this.get('maxHeight'))); $el.css('overflowY', (el.scrollHeight > neu ? 'auto' : 'hidden')); diff --git a/app/styles/layout/_layout.scss b/app/styles/layout/_layout.scss index 1f107c1d5..bace2ba55 100644 --- a/app/styles/layout/_layout.scss +++ b/app/styles/layout/_layout.scss @@ -29,10 +29,22 @@ B { background-color : $midGray; } - .inline-block { display : inline-block; } + +.valign-top { + vertical-align: top; +} + +.valign-middle { + vertical-align: middle; +} + +.valign-bottom { + vertical-align: bottom; +} + /********** * Articles (above the page info/warnings) **********/ From fccf91480c9d30b6e8619000501263aa063d9c04 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Thu, 7 Apr 2016 16:02:58 -0700 Subject: [PATCH 3/4] No strategy on external-service healthchecks (rancher/rancher#4326) --- app/components/form-healthcheck/component.js | 2 ++ app/components/form-healthcheck/template.hbs | 2 +- app/components/form-key-value/component.js | 18 +++++++++++------- .../new-externalservice/template.hbs | 2 ++ app/components/textarea-autogrow/component.js | 2 +- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/components/form-healthcheck/component.js b/app/components/form-healthcheck/component.js index 3a00ba58a..2861cb9f9 100644 --- a/app/components/form-healthcheck/component.js +++ b/app/components/form-healthcheck/component.js @@ -24,6 +24,8 @@ export default Ember.Component.extend({ // Inputs healthCheck: null, errors: null, + isService: null, + showStrategy: true, tagName: '', diff --git a/app/components/form-healthcheck/template.hbs b/app/components/form-healthcheck/template.hbs index 3eb8108bc..0c87ecaef 100644 --- a/app/components/form-healthcheck/template.hbs +++ b/app/components/form-healthcheck/template.hbs @@ -128,7 +128,7 @@ - {{#if isService}} + {{#if (and isService showStrategy)}}
diff --git a/app/components/form-key-value/component.js b/app/components/form-key-value/component.js index 32b4c7ebd..5b4f17a92 100644 --- a/app/components/form-key-value/component.js +++ b/app/components/form-key-value/component.js @@ -66,7 +66,6 @@ export default Ember.Component.extend({ allowMultilineValue: true, ary: null, - asMap: null, actions: { add() { @@ -122,9 +121,17 @@ export default Ember.Component.extend({ } }, - asMapObserver: function() { + aryObserver: function() { + Ember.run.debounce(this,'fireChanged',100); + }.observes('ary.@each.{key,value}'), + + fireChanged() { + if ( this._state === 'destroying' ) + { + return; + } + var out = {}; - var outStr = ''; this.get('ary').forEach((row) => { var k = row.get('key').trim(); @@ -133,12 +140,9 @@ export default Ember.Component.extend({ if ( k && (v || this.get('allowEmptyValue')) ) { out[row.get('key').trim()] = row.get('value').trim(); - outStr += (outStr ? ',' : '') + k + '=' + v; } }); - this.set('asMap', out); this.sendAction('changed', out); - this.sendAction('changedStr', outStr); - }.observes('ary.@each.{key,value}'), + }, }); diff --git a/app/components/new-externalservice/template.hbs b/app/components/new-externalservice/template.hbs index b6ea4c54b..2beddc30c 100644 --- a/app/components/new-externalservice/template.hbs +++ b/app/components/new-externalservice/template.hbs @@ -19,8 +19,10 @@
{{form-healthcheck isService=true + showStrategy=false healthCheck=service.healthCheck }} +
Note: This will only be used if the external service is a target of a load balancer.
{{top-errors errors=errors}} diff --git a/app/components/textarea-autogrow/component.js b/app/components/textarea-autogrow/component.js index 2bbfcbd5b..889452775 100644 --- a/app/components/textarea-autogrow/component.js +++ b/app/components/textarea-autogrow/component.js @@ -19,7 +19,7 @@ export default Ember.TextArea.extend({ }, keyUp() { - this.autoSize(); + Ember.run.debounce(this,'autoSize',100); }, isSmall: function() { From 94df32acf7442ada02541c223c395b9d8a9dfb8b Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Thu, 7 Apr 2016 19:03:13 -0700 Subject: [PATCH 4/4] Fix boundAddress port editing (rancher/rancher#4280) --- app/components/form-ports/component.js | 11 ++++------- app/components/form-ports/template.hbs | 15 ++++++++++++--- app/container/ports/template.hbs | 4 ++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/components/form-ports/component.js b/app/components/form-ports/component.js index b17b2e1a1..1720b0737 100644 --- a/app/components/form-ports/component.js +++ b/app/components/form-ports/component.js @@ -34,18 +34,15 @@ export default Ember.Component.extend({ if ( typeof value === 'object' ) { var existing = !forceNew && !!value.id; - var pub = ''; - if ( value.publicPort ) + var pub = value.publicPort+''; + if ( !existing && value.bindAddress ) { - if ( value.bindAddress ) { - pub += value.bindAddress + ':'; - } - - pub += value.publicPort; + pub = value.bindAddress + ':' + pub; } out.push({ existing: existing, obj: value, + bindAddress: value.bindAddress||null, public: pub, private: value.privatePort, protocol: value.protocol, diff --git a/app/components/form-ports/template.hbs b/app/components/form-ports/template.hbs index f2aa84457..61cf8c5ba 100644 --- a/app/components/form-ports/template.hbs +++ b/app/components/form-ports/template.hbs @@ -10,9 +10,9 @@ {{#if portsArray.length}} - + - + @@ -20,7 +20,16 @@ {{#each portsArray as |port|}} - - + +
Public (on Host) IP/PortPublic Host [IP:]Port  Private (in Container) PortPrivate Container Port   Protocol  
- {{input class="form-control input-sm" type="text" value=port.public placeholder="e.g. 80 or 1.2.3.4:80"}} + {{#if (and port.existing port.bindAddress)}} +
+ + {{port.bindAddress}}: + + {{input class="form-control input-sm" type="text" value=port.public placeholder="e.g. 80"}} +
+ {{else}} + {{input class="form-control input-sm" type="text" value=port.public placeholder="e.g. 80 or 1.2.3.4:80"}} + {{/if}}

diff --git a/app/container/ports/template.hbs b/app/container/ports/template.hbs index 37fc94252..8f7428dbb 100644 --- a/app/container/ports/template.hbs +++ b/app/container/ports/template.hbs @@ -3,8 +3,8 @@
State IP AddressPublic (on Host)Private (in Container)Public on HostPrivate in Container Protocol