LoadBalancer WIP

This commit is contained in:
Vincent Fiduccia 2015-04-06 00:31:25 -07:00
parent 6c7134627a
commit 818991523a
26 changed files with 682 additions and 255 deletions

View File

@ -27,18 +27,8 @@
</div>
<div class="clearfix no-resource-action-hover">
{{#each item in model.arrangedTargets}}
{{#if item.ipAddress}}
<p>IP: {{item.ipAddress}}</p>
{{else}}
{{#if item.instance}}
{{#with item.instance as c controller="container"}}
<p>Container: {{c.displayName}}</p>
{{/with}}
{{else}}
<p>Instance?: {{item.instanceId}}</p>
{{/if}}
{{/if}}
{{#each item in model.arrangedTargets itemController="loadbalancertarget"}}
{{loadbalancertarget-subpod model=item}}
{{/each}}
{{#if showAdd}}
{{add-subpod action="newTarget" label="Add Target"}}

View File

@ -0,0 +1,16 @@
import Ember from 'ember';
import HoverActions from 'ui/mixins/hover-actions';
export default Ember.Component.extend(HoverActions, {
model: null,
classNames: ['subpod','resource-action-hover'],
click: function() {
// For touch devices, show actions on a click anywhere in the component
if ( $('BODY').hasClass('touch') )
{
this.send('showActions');
}
},
});

View File

@ -0,0 +1,41 @@
{{resource-actions-menu model=model choices=model.availableActions}}
<div class="subpod-name">
{{#if model.isIp}}
<i class="ss-record text-success"></i>
{{model.ipAddress}}
{{else}}
{{#if model.instance}}
{{#with model.instance as container controller="container"}}
<i {{bind-attr class="container.stateIcon container.stateColor" tooltip=container.displayState}}></i>
{{#link-to "container" model.instanceId}}
{{container.displayName}}
{{/link-to}}
{{/with}}
{{else}}
<i class="ss-record-text-success" style="visibility: hidden"></i>
<span class="text-muted">Loading...</span>
{{/if}}
{{/if}}
</div>
{{#if model.showTransitioningMessage}}
<div {{bind-attr class=":subpod-detail :clip model.isError:text-danger"}}>
{{model.transitioningMessage}}
</div>
{{else}}
<div class="subpod-detail">
{{#if model.isIp}}
IP Address
{{else}}
Container
{{/if}}
</div>
{{/if}}
{{#if model.isTransitioning}}
<div class="progress progress-striped active">
<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" {{bind-attr aria-valuenow=model.displayProgress style=model.progressStyle}}>
<span class="sr-only">{{model.displayProgress}}% Complete</span>
</div>
</div>
{{/if}}

View File

@ -1,137 +1,5 @@
<section class="text-right">
<button {{action "editConfig"}} class="btn btn-sm btn-primary">Edit Configuration</button>
</section>
<div class="well section">
<div class="row">
<div class="col-sm-6">
<h4>Listeners ({{listeners.length}})</h4>
</div>
</div>
<table class="grid fixed" style="margin-bottom: 0;">
<thead>
<tr>
<th width="120">State</th>
<th>Source</th>
<th width="30"></th>
<th>Target</th>
<th class="text-right">Algorithm</th>
</tr>
</thead>
<tbody>
{{#each listener in listeners itemController="loadbalancerlistener"}}
<tr>
<td>
<span {{bind-attr class=":badge :state listener.stateBackground"}}>
{{listener.displayState}}
</span>
</td>
<td>
{{listener.sourcePort}}/{{listener.sourceProtocol}}
</td>
<td>
<i class="ss-arrow-right"></i>
</td>
<td>
{{listener.targetPort}}/{{listener.targetProtocol}}
</td>
<td class="text-right">
{{listener.algorithm}}
</td>
</tr>
{{else}}
<tr><td colspan="4" class="text-center text-muted">This host does not have any containers yet.</td></tr>
{{/each}}
</tbody>
</table>
</div>
<div class="well section">
<h4 style="margin-bottom: 20px;">Health Check</h4>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>URL</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">
{{#if config.healthCheck.uri}}
{{config.healthCheck.uri}}
{{else}}
<i>None</i>
{{/if}}
</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Check Interval</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.interval}}ms</span>
</div>
<div class="col-sm-12 col-md-3">
<label>Timeout</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.responseTimeout}}ms</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Healthy Threshold</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.healthyThreshold}}ms</span>
</div>
<div class="col-sm-12 col-md-3">
<label>Unealthy Threshold</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.unhealthyThreshold}}ms</span>
</div>
</div>
</div>
<div class="well section">
<h4>Stickiness</h4>
{{#if config.appCookieStickinessPolicy}}
{{else}}
{{#if config.lbCookieStickinessPolicy}}
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Cookie Name</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.lbCookieStickinessPolicy.cookie}}</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Cookie Domain</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.lbCookieStickinessPolicy.domain}}</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Mode</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">
{{config.lbCookieStickinessPolicy.mode}}
{{#if config.lbCookieStickinessPolicy.indirect}}, indirect{{/if}}
{{#if config.lbCookieStickinessPolicy.nocache}}, no-cache{{/if}}
{{#if config.lbCookieStickinessPolicy.postonly}}, POST only{{/if}}
</span>
</div>
</div>
{{else}}
<span class="text-muted">Stickiness is not configured.</span>
{{/if}}
{{/if}}
</div>
{{partial "loadbalancer/config-detail"}}

View File

@ -2,6 +2,9 @@ import Ember from 'ember';
export default Ember.Route.extend({
model: function(/*params*/) {
return this.modelFor('loadbalancer');
var balancer = this.modelFor('loadbalancer');
return balancer.importLink('hosts',{sort: 'name', include: 'ipAddresses'}).then(() => {
return balancer;
});
}
});

View File

@ -5,6 +5,15 @@ export default Ember.ObjectController.extend({
return !this.get('actions.settargets');
}.property('actions.settargets'),
arrangedTargets: function() {
var targets = this.get('loadBalancerTargets');
return Ember.ArrayController.create({
content: targets,
sortProperties: ['ipAddress', 'instance.name', 'instance.id', 'instanceId']
});
}.property('instances.[]','loadBalancerTargets.@each.{instanceId,ipAddress}'),
actions: {
newTarget: function() {
this.transitionToRoute('loadbalancer.targets.new');

View File

@ -1,7 +1,7 @@
<section class="grid-header">
<div class="row">
<div class="col-sm-6">
<h3>Targets ({{loadBalancerTargets.length}})</h3>
<h3>Targets ({{arrangedTargets.length}})</h3>
</div>
<div class="col-sm-6 text-right">
<button {{action "newTarget"}} class="btn btn-sm btn-primary" {{bind-attr disabled="addDisabled:disabled"}}>Add Targets</button>
@ -19,7 +19,7 @@
</tr>
</thead>
<tbody>
{{#each target in loadBalancerTargets itemController="loadbalancertarget"}}
{{#each target in arrangedTargets itemController="loadbalancertarget"}}
<tr>
<td>
<span {{bind-attr class=":badge :state target.stateBackground"}}>
@ -34,9 +34,13 @@
Container
</td>
<td>
{{#with target.instance as c controller="container"}}
{{#link-to "container" c.id}}{{c.displayName}}{{/link-to}}
{{/with}}
{{#if target.instance}}
{{#with target.instance as c controller="container"}}
{{#link-to "container" c.id}}{{c.displayName}}{{/link-to}}
{{/with}}
{{else}}
<span class="text-muted">Loading...</span>
{{/if}}
</td>
{{/if}}
<td class="actions">

View File

@ -0,0 +1 @@
{{partial "loadbalancer/config-detail"}}

View File

@ -2,9 +2,14 @@ import Cattle from 'ui/utils/cattle';
var LoadBalancerConfig = Cattle.TransitioningResource.extend({
type: 'loadBalancerConfig',
listeners: Ember.computed.alias('loadBalancerListeners'),
config: function() {
return this;
}.property()
});
LoadBalancerConfig.reopenClass({
alwaysInclude: ['loadBalancerListeners','loadBalancers'],
});
export default LoadBalancerConfig;

View File

@ -0,0 +1,11 @@
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.get('store').find('loadbalancerconfig', params.loadbalancerconfig_id);
},
activate: function() {
this.send('setPageLayout', {label: 'All Balancer Configs', backRoute: 'loadbalancerconfigs', hasAside: 'nav-balancing active'});
},
});

View File

@ -0,0 +1,38 @@
<aside>
<label>Balancer Config</label>
{{resource-actions-menu model=this choices=availableActions classNames="pull-right"}}
<h3>{{displayName}}</h3>
<hr/>
{{#if description}}
<label style="margin-top: 10px;">Description</label>
<p>{{description}}</p>
<hr/>
{{/if}}
<div class="clearfix">
<label>Info</label>
</div>
<ul class="list-circles">
<li>
<i {{bind-attr class=":fa-fw stateIcon"}}></i>
{{displayState}}
{{#if isTransitioning}}
<div class="progress progress-striped active" style="height: 10px; border: 0;">
<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" {{bind-attr aria-valuenow=displayProgress style=progressStyle}}>
<span class="sr-only">{{displayProgress}}% Complete</span>
</div>
</div>
{{/if}}
{{#if showTransitioningMessage}}
<div class="force-wrap">
{{transitioningMessage}}
</div>
{{/if}}
</li>
</ul>
</aside>
{{outlet}}

View File

@ -0,0 +1,5 @@
import Cattle from 'ui/utils/cattle';
export default Cattle.CollectionController.extend({
itemController: 'loadbalancerconfig'
});

View File

@ -1,3 +1,56 @@
{{partial "balancing-aside"}}
Configs grid...
<section>
<table class="grid fixed" style="margin-bottom: 0">
<thead>
<tr>
<th width="120">State</th>
<th>Name</th>
<th width="100">Listeners</th>
<th width="100">Stickiness</th>
<th width="150">Used by</th>
<th width="50">&nbsp;</th>
</tr>
</thead>
<tbody>
{{#each config in this}}
<tr>
<td>
<span {{bind-attr class=":badge :state config.stateBackground"}}>
{{config.displayState}}
</span>
</td>
<td>
{{#link-to "loadbalancerconfig" config.id}}{{config.displayName}}{{/link-to}}
</td>
<td>
{{#if config.loadBalancerListeners}}{{config.loadBalancerListeners.length}}{{else}}<i class="text-muted">None</i>{{/if}}
</td>
<td>
{{#if config.appCookieStickinessPolicy}}
Create Cookie
{{else}}
{{#if config.lbCookieStickinessPolicy}}
Use existing cookie
{{else}}
None
{{/if}}
{{/if}}
</td>
<td>
{{#each balancer in config.loadBalancers itemController="loadbalancer"}}
<p>{{#link-to "loadbalancer" balancer.id}}{{balancer.displayName}}{{/link-to}}</p>
{{else}}
<span class="text-muted">None</span>
{{/each}}
</td>
<td align="right">
{{resource-actions-menu model=config choices=config.availableActions}}
</td>
</tr>
{{else}}
<tr><td colspan="4" class="text-center text-muted">You don't have any registries yet.</td></tr>
{{/each}}
</tbody>
</table>
</section>

View File

@ -1,4 +1,11 @@
import Ember from 'ember';
export default Ember.View.extend({
didInsertElement: function() {
$('BODY').addClass('white');
},
willDestroyElement: function() {
$('BODY').removeClass('white');
},
});

View File

@ -0,0 +1,45 @@
import Ember from 'ember';
import Cattle from 'ui/utils/cattle';
import EditLoadBalancerConfig from 'ui/mixins/edit-loadbalancerconfig';
export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, EditLoadBalancerConfig, {
queryParams: ['tab'],
tab: 'listeners',
error: null,
editing: false,
primaryResource: Ember.computed.alias('model.config'),
initFields: function() {
this.set('listenersArray', [
this.get('store').createRecord({
type: 'loadBalancerListener',
name: 'uilistener',
sourcePort: '',
sourceProtocol: 'tcp',
targetPort: '',
targetProtocol: 'tcp',
algorithm: 'roundrobin',
})
]);
},
didSave: function() {
var listeners = this.get('listenersArray');
var promises = [];
listeners.forEach((listener) => {
promises.push(listener.save());
});
return Ember.RSVP.all(promises).then((listeners) => {
var ids = listeners.map((listener) => {
return listener.get('id');
});
return this.get('config').doAction('setlisteners',{loadBalancerListenerIds: ids});
});
},
doneSaving: function() {
this.transitionToRoute('loadbalancerconfigs');
},
});

View File

@ -0,0 +1,61 @@
import AuthenticatedRouteMixin from 'ui/mixins/authenticated-route';
import Ember from 'ember';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(/*params, transition*/) {
var store = this.get('store');
var dependencies = [
store.findAll('host'),
];
return Ember.RSVP.all(dependencies, 'Load dependencies').then(function(results) {
return {
config: store.createRecord({
type: 'loadBalancerConfig',
healthCheck: store.createRecord({
type: 'loadBalancerHealthCheck',
interval: 2000,
responseTimeout: 2000,
healthyThreshold: 2,
unhealthyThreshold: 3,
}),
appCookieStickinessPolicy: null,
lbCookieStickinessPolicy: null,
}),
appCookie: store.createRecord({
type: 'loadBalancerAppCookieStickinessPolicy',
mode: 'path_parameters',
requestLearn: true,
timeout: 3600000,
}),
lbCookie: store.createRecord({
type: 'loadBalancerCookieStickinessPolicy'
}),
};
});
},
setupController: function(controller, model) {
controller.set('model',model);
controller.initFields();
},
resetController: function (controller, isExisting/*, transition*/) {
if (isExisting)
{
controller.set('tab', 'listeners');
controller.set('stickiness', 'none');
}
},
activate: function() {
this.send('setPageLayout', {label: 'Back', backRoute: 'loadbalancers'});
},
actions: {
cancel: function() {
this.transitionTo('loadbalancers');
},
}
});

View File

@ -0,0 +1,32 @@
<section class="horizontal-form container-fluid">
<h2>Add Balancer Config</h2>
{{partial "top-error"}}
<div class="row form-group">
<div class="col-sm-12 col-md-2 form-label">
<label for="name">Name</label>
</div>
<div class="col-sm-12 col-md-8">
{{input id="name" type="text" value=config.name classNames="form-control" placeholder="e.g. Web balancer config"}}
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-2 form-label">
<label for="description">Description</label>
</div>
<div class="col-sm-12 col-md-8">
{{textarea id="description" value=config.description classNames="form-control no-resize" rows="5" placeholder="e.g. HTTP balancing for www.example.com"}}
</div>
</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<hr/>
</div>
</div>
</section>
{{partial "loadbalancer/edit-config"}}
{{partial "save-cancel"}}

View File

@ -0,0 +1,39 @@
import Ember from 'ember';
function addAction(action, selector) {
return function() {
this.get('controller').send(action);
Ember.run.next(this, function() {
this.$(selector).last().focus();
});
};
}
export default Ember.View.extend({
actions: {
addHost: addAction('addHost', '.lb-host'),
addTargetContainer: addAction('addTargetContainer', '.lb-target'),
addTargetIp: addAction('addTargetIp', '.lb-target'),
addListener: addAction('addListener', '.lb-listener-source-port'),
selectTab: function(name) {
this.set('context.tab',name);
this.$('.tab').removeClass('active');
this.$('.tab[data-section="'+name+'"]').addClass('active');
this.$('.section').addClass('hide');
this.$('.section[data-section="'+name+'"]').removeClass('hide');
}
},
didInsertElement: function() {
$('BODY').addClass('white');
this._super();
this.send('selectTab',this.get('context.tab'));
this.$('INPUT')[0].focus();
},
willDestroyElement: function() {
$('BODY').removeClass('white');
},
});

View File

@ -17,7 +17,7 @@ export default HostController.extend({
availableActions: function() {
var choices = [
{ label: 'Delete', icon: 'ss-trash', action: 'promptDelete', enabled: true, altAction: 'delete' },
{ label: 'Remove Host', icon: 'ss-trash', action: 'promptDelete', enabled: true, altAction: 'delete' },
];
return choices;

View File

@ -1,7 +1,8 @@
import Ember from 'ember';
import Cattle from 'ui/utils/cattle';
import EditLoadBalancerConfig from 'ui/mixins/edit-loadbalancerconfig';
export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, {
export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, EditLoadBalancerConfig, {
queryParams: ['tab'],
tab: 'listeners',
error: null,
@ -25,26 +26,6 @@ export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, {
removeTarget: function(obj) {
this.get('targetsArray').removeObject(obj);
},
addListener: function() {
this.get('listenersArray').pushObject(this.get('store').createRecord({
type: 'loadBalancerListener',
name: 'uilistener',
sourcePort: '',
sourceProtocol: 'tcp',
targetPort: '',
targetProtocol: 'tcp',
algorithm: 'roundrobin',
}));
},
removeListener: function(obj) {
this.get('listenersArray').removeObject(obj);
},
chooseProtocol: function(listener, key, val) {
listener.set(key,val);
},
},
initFields: function() {
@ -113,52 +94,6 @@ export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, {
});
}.property('targetsArray.@each.{isIp,isContainer,value}'),
sourceProtocolOptions: function() {
return this.get('store').getById('schema','loadbalancerlistener').get('resourceFields.sourceProtocol.options');
}.property(),
targetProtocolOptions: function() {
return this.get('store').getById('schema','loadbalancerlistener').get('resourceFields.targetProtocol.options');
}.property(),
algorithmOptions: function() {
return this.get('store').getById('schema','loadbalancerlistener').get('resourceFields.algorithm.options');
}.property(),
listenersArray: null,
stickiness: 'none',
isStickyNone: Ember.computed.equal('stickiness','none'),
isStickyLbCookie: Ember.computed.equal('stickiness','lbCookie'),
isStickyAppCookie: Ember.computed.equal('stickiness','appCookie'),
lbCookieModeChoices: [
{value: 'rewrite', label: 'Rewrite'},
{value: 'insert', label: 'Insert'},
{value: 'prefix', label: 'Prefix'},
],
appCookieModeChoices: [
{value: 'path_parameters', label: 'Path Parameter'},
{value: 'query_string', label: 'Query String'},
],
stickinessDidChange: function() {
var stickiness = this.get('stickiness');
if ( stickiness === 'none' )
{
this.set('config.lbCookieStickinessPolicy', null);
this.set('config.appCookieStickinessPolicy', null);
}
else if ( stickiness === 'lbCookie' )
{
this.set('config.lbCookieStickinessPolicy', this.get('lbCookie'));
this.set('config.appCookieStickinessPolicy', null);
}
else if ( stickiness === 'appCookie' )
{
this.set('config.lbCookieStickinessPolicy', null);
this.set('config.appCookieStickinessPolicy', this.get('appCookie'));
}
}.observes('stickiness'),
validate: function() {
var config = this.get('model.config');
var balancer = this.get('model.balancer');

View File

@ -70,37 +70,6 @@
</div>
</div>
<section class="text-center" style="padding: 0;">
<ul class="nav nav-pills" style="display: inline-block">
<li role="presentation" class="tab" data-section="listeners" {{action "selectTab" "listeners" target=view}}><a>Listeners</a></li>
<li role="presentation" class="tab" data-section="healthcheck" {{action "selectTab" "healthcheck" target=view}}><a>Health Check</a></li>
<li role="presentation" class="tab" data-section="stickiness" {{action "selectTab" "stickiness" target=view}}><a>Stickiness</a></li>
</ul>
</section>
<section class="horizontal-form tab-section" style="background-color: #f8f9fa; margin: -7px 10px 0 10px;">
<div class="section container-fluid" data-section="listeners">
<div class="row">
<div class="col-xs-12">
<label>Listening Ports</label>
&nbsp;
<button class="btn-circle-plus" {{action "addListener" target="view"}}></button>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
{{partial "loadbalancer/new-listeners"}}
</div>
</div>
</div>
<div class="section container-fluid tab-section" data-section="healthcheck">
{{partial "loadbalancer/edit-healthcheck"}}
</div>
<div class="section container-fluid tab-section" data-section="stickiness">
{{partial "loadbalancer/edit-stickiness"}}
</div>
</section>
{{partial "loadbalancer/edit-config"}}
{{partial "save-cancel"}}

View File

@ -13,14 +13,19 @@ var LoadBalancerTargetController = Cattle.TransitioningResourceController.extend
});
},
actions: {
delete: function() {
this.delete();
}
},
availableActions: function() {
var a = this.get('actions');
var choices = [
{ label: 'Delete', icon: 'ss-trash', action: 'promptDelete', enabled: !!a.remove, altAction: 'delete' },
{ label: 'Remove Target', icon: 'ss-trash', action: 'promptDelete', enabled: true, altAction: 'delete' },
{ label: 'Purge', icon: 'ss-tornado', action: 'purge', enabled: !!a.purge },
{ divider: true },
{ label: 'View in API', icon: 'fa fa-external-link', action: 'goToApi', enabled: true, detail: true },
];
return choices;

View File

@ -0,0 +1,74 @@
import Ember from 'ember';
export default Ember.Mixin.create({
actions: {
addListener: function() {
this.get('listenersArray').pushObject(this.get('store').createRecord({
type: 'loadBalancerListener',
name: 'uilistener',
sourcePort: '',
sourceProtocol: 'tcp',
targetPort: '',
targetProtocol: 'tcp',
algorithm: 'roundrobin',
}));
},
removeListener: function(obj) {
this.get('listenersArray').removeObject(obj);
},
chooseProtocol: function(listener, key, val) {
listener.set(key,val);
},
},
listenersArray: null,
sourceProtocolOptions: function() {
return this.get('store').getById('schema','loadbalancerlistener').get('resourceFields.sourceProtocol.options');
}.property(),
targetProtocolOptions: function() {
return this.get('store').getById('schema','loadbalancerlistener').get('resourceFields.targetProtocol.options');
}.property(),
algorithmOptions: function() {
return this.get('store').getById('schema','loadbalancerlistener').get('resourceFields.algorithm.options');
}.property(),
stickiness: 'none',
isStickyNone: Ember.computed.equal('stickiness','none'),
isStickyLbCookie: Ember.computed.equal('stickiness','lbCookie'),
isStickyAppCookie: Ember.computed.equal('stickiness','appCookie'),
lbCookieModeChoices: [
{value: 'rewrite', label: 'Rewrite'},
{value: 'insert', label: 'Insert'},
{value: 'prefix', label: 'Prefix'},
],
appCookieModeChoices: [
{value: 'path_parameters', label: 'Path Parameter'},
{value: 'query_string', label: 'Query String'},
],
stickinessDidChange: function() {
var stickiness = this.get('stickiness');
if ( stickiness === 'none' )
{
this.set('config.lbCookieStickinessPolicy', null);
this.set('config.appCookieStickinessPolicy', null);
}
else if ( stickiness === 'lbCookie' )
{
this.set('config.lbCookieStickinessPolicy', this.get('lbCookie'));
this.set('config.appCookieStickinessPolicy', null);
}
else if ( stickiness === 'appCookie' )
{
this.set('config.lbCookieStickinessPolicy', null);
this.set('config.appCookieStickinessPolicy', this.get('appCookie'));
}
}.observes('stickiness'),
});

View File

@ -99,6 +99,11 @@ Router.map(function() {
this.resource('loadbalancerconfigs', {path: '/configs'}, function() {
this.route('new', {path: '/add'});
this.route('index', {path: '/'});
this.resource('loadbalancerconfig', {path: '/:loadbalancerconfig_id'}, function() {
this.route('index', {path: '/'});
this.route('edit');
});
});
});
});

View File

@ -0,0 +1,178 @@
<div class="well section">
<div class="row">
<div class="col-sm-6">
<h4>Listeners ({{listeners.length}})</h4>
</div>
</div>
<table class="grid fixed" style="margin-bottom: 0;">
<thead>
<tr>
<th width="120">State</th>
<th>Source</th>
<th width="30"></th>
<th>Target</th>
<th class="text-right">Algorithm</th>
</tr>
</thead>
<tbody>
{{#each listener in listeners itemController="loadbalancerlistener"}}
<tr>
<td>
<span {{bind-attr class=":badge :state listener.stateBackground"}}>
{{listener.displayState}}
</span>
</td>
<td>
{{listener.sourcePort}}/{{listener.sourceProtocol}}
</td>
<td>
<i class="ss-arrow-right"></i>
</td>
<td>
{{listener.targetPort}}/{{listener.targetProtocol}}
</td>
<td class="text-right">
{{listener.algorithm}}
</td>
</tr>
{{else}}
<tr><td colspan="4" class="text-center text-muted">This host does not have any containers yet.</td></tr>
{{/each}}
</tbody>
</table>
</div>
<div class="well section">
<h4 style="margin-bottom: 20px;">Health Check</h4>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>URL</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">
{{#if config.healthCheck.uri}}
{{config.healthCheck.uri}}
{{else}}
<i>None</i>
{{/if}}
</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Check Interval</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.interval}}ms</span>
</div>
<div class="col-sm-12 col-md-3">
<label>Timeout</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.responseTimeout}}ms</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Healthy Threshold</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.healthyThreshold}}ms</span>
</div>
<div class="col-sm-12 col-md-3">
<label>Unealthy Threshold</label>
</div>
<div class="col-sm-12 col-md-3">
<span class="form-control-static">{{config.healthCheck.unhealthyThreshold}}ms</span>
</div>
</div>
</div>
<div class="well section">
<h4 style="margin-bottom: 20px;">Stickiness</h4>
{{#if config.appCookieStickinessPolicy}}
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Cookie Name</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.appCookieStickinessPolicy.cookie}}</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Prefix</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.appCookieStickinessPolicy.prefix}}</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Maximum Length</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.appCookieStickinessPolicy.length}}</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Timeout</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.appCookieStickinessPolicy.timeout}}ms</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Mode</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">
{{config.appCookieStickinessPolicy.mode}}
{{#if config.appCookieStickinessPolicy.requestLearn}}, lean from request{{/if}}
</span>
</div>
</div>
{{else}}
{{#if config.lbCookieStickinessPolicy}}
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Cookie Name</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.lbCookieStickinessPolicy.cookie}}</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Cookie Domain</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">{{config.lbCookieStickinessPolicy.domain}}</span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-3">
<label>Mode</label>
</div>
<div class="col-sm-12 col-md-9">
<span class="form-control-static">
{{config.lbCookieStickinessPolicy.mode}}
{{#if config.lbCookieStickinessPolicy.indirect}}, indirect{{/if}}
{{#if config.lbCookieStickinessPolicy.nocache}}, no-cache{{/if}}
{{#if config.lbCookieStickinessPolicy.postonly}}, POST only{{/if}}
</span>
</div>
</div>
{{else}}
<span class="text-muted">Stickiness is not configured.</span>
{{/if}}
{{/if}}
</div>

View File

@ -0,0 +1,33 @@
<section class="text-center" style="padding: 0;">
<ul class="nav nav-pills" style="display: inline-block">
<li role="presentation" class="tab" data-section="listeners" {{action "selectTab" "listeners" target=view}}><a>Listeners</a></li>
<li role="presentation" class="tab" data-section="healthcheck" {{action "selectTab" "healthcheck" target=view}}><a>Health Check</a></li>
<li role="presentation" class="tab" data-section="stickiness" {{action "selectTab" "stickiness" target=view}}><a>Stickiness</a></li>
</ul>
</section>
<section class="horizontal-form tab-section" style="background-color: #f8f9fa; margin: -7px 10px 0 10px;">
<div class="section container-fluid" data-section="listeners">
<div class="row">
<div class="col-xs-12">
<label>Listening Ports</label>
&nbsp;
<button class="btn-circle-plus" {{action "addListener" target="view"}}></button>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2">
{{partial "loadbalancer/new-listeners"}}
</div>
</div>
</div>
<div class="section container-fluid tab-section" data-section="healthcheck">
{{partial "loadbalancer/edit-healthcheck"}}
</div>
<div class="section container-fluid tab-section" data-section="stickiness">
{{partial "loadbalancer/edit-stickiness"}}
</div>
</section>