Service Upgrade

This commit is contained in:
Vincent Fiduccia 2015-10-23 15:48:39 -07:00
parent 69f901ce61
commit 2262a91372
13 changed files with 235 additions and 64 deletions

View File

@ -13,9 +13,9 @@ export default Ember.Component.extend({
didInitAttrs() {
var initial;
if ( this.get('value') )
if ( this.get('initialValue') )
{
initial = (this.get('value')||'').replace(/^docker:/,'');
initial = (this.get('initialValue')||'').replace(/^docker:/,'');
}
if ( !initial )

View File

@ -4,6 +4,7 @@ export default Ember.Component.extend({
// Inputs
model: null,
namePlaceholder: '',
nameDisabled: false,
descriptionPlaceholder: '',
tagName: '',

View File

@ -3,7 +3,7 @@
<label class="form-control-static">Name</label>
</div>
<div class="col-sm-12 col-md-8">
{{input type="text" value=model.name classNames="form-control" placeholder=namePlaceholder}}
{{input type="text" value=model.name classNames="form-control" placeholder=namePlaceholder disabled=nameDisabled}}
</div>
</div>

View File

@ -0,0 +1,19 @@
import Ember from 'ember';
export default Ember.Component.extend({
batchSize: 1,
interval: 2,
startFirst: false,
didInitAttrs() {
this.optionsChanged();
},
optionsChanged() {
this.sendAction('changed', {
batchSize: this.get('batchSize'),
intervalMillis: this.get('interval')*1000,
startFirst: this.get('startFirst'),
});
},
});

View File

@ -0,0 +1,25 @@
<div class="row">
<div class="col-sm-12 col-md-2 form-label">
<label>Upgrade</label>
</div>
<div class="col-sm-12 col-md-8">
<div class="row">
<div class="col-md-4">
<label>Batch Size</label>
{{input type="number" min="1" class="form-control" value=batchSize}}
</div>
<div class="col-md-4">
<label>Batch Interval</label>
<div class="input-group">
{{input type="number" class="form-control" value=interval}}
<span class="input-group-addon">sec</span>
</div>
</div>
<div class="col-md-4">
<label>Start Behavior</label>
<label class="form-control-static" style="color: inherit; text-transform: none;">{{input type="checkbox" checked=startFirst}} Start before Stop</label>
</div>
</div>
</div>
</div>

View File

@ -7,6 +7,7 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
isStandalone: true,
isService: false,
isSidekick: false,
isUpgrade: false,
primaryResource: null,
launchConfig: null,
service: null,
@ -18,6 +19,7 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
isRequestedHost: null,
portsAsStrArray: null,
launchConfigIndex: -1,
upgradeOptions: null,
// Errors from components
commandErrors: null,
@ -90,6 +92,10 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
this.set('serviceLinksArray', links);
},
setUpgrade(upgrade) {
this.set('upgradeOptions', upgrade);
},
done() {
this.sendAction('done');
},
@ -124,6 +130,48 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
}
}.property('launchConfigIndex'),
activeLabel: function() {
var idx = this.get('launchConfigIndex');
var str = '';
if ( this.get('hasSidekicks') )
{
if ( idx === -1 )
{
str = 'Primary Service: ';
}
else
{
str += 'Sidekick Service: ';
}
}
if ( idx === -1 )
{
if ( this.get('service.name') )
{
str += this.get('service.name');
}
else
{
str += '(No name)';
}
}
else
{
if ( this.get('activeLaunchConfig.name') )
{
str += this.get('activeLaunchConfig.name');
}
else
{
str += '(Sidekick #' + (idx+1) + ')';
}
}
return str;
}.property('service.name','activeLaunchConfig.name','launchConfigIndex','hasSidekicks'),
// ----------------------------------
// Labels
// ----------------------------------
@ -178,8 +226,27 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
return this._super.apply(this,arguments);
},
doSave() {
if ( this.get('isService') && this.get('isUpgrade') )
{
return this.get('service').doAction('upgrade', {
inServiceStrategy: {
batchSize: this.get('upgradeOptions.batchSize'),
intervalMillis: this.get('upgradeOptions.intervalMillis'),
launchConfig: this.get('service.launchConfig'),
secondaryLaunchConfigs: this.get('service.secondaryLaunchConfigs'),
startFirst: this.get('upgradeOptions.startFirst'),
},
});
}
else
{
return this._super.apply(this,arguments);
}
},
didSave() {
if ( this.get('isService') )
if ( this.get('isService') && !this.get('isUpgrade') )
{
// Returns a promise
return this.setServiceLinks();

View File

@ -1,6 +1,6 @@
{{#unless isSidekick}}
<section class="horizontal-form container-fluid">
<h2>Add
<h2>{{if isUpgrade 'Upgrade' 'Add'}}
{{#if isService}}
Service
{{#if hasSidekicks}}
@ -13,7 +13,7 @@
</h2>
{{top-errors errors=errors}}
{{#if (and isService (not isSidekick))}}
{{#if isService}}
{{form-scale
initialLabels=launchConfig.labels
initialScale=service.scale
@ -24,25 +24,29 @@
setScale=(action 'setScale')
}}
{{form-divider}}
{{#if isUpgrade}}
{{form-upgrade
changed=(action 'setUpgrade')
}}
{{/if}}
{{#if hasSidekicks}}
<div class="row">
<div class="col-sm-12 col-md-2 form-label r-mt5">
<label>Service</label>
</div>
<div class="col-sm-12 col-md-8">
{{#if (and isUpgrade (not hasSidekicks))}}
{{form-divider}}
{{else}}
<div class="over-hr r-mt5">
<span style="text-transform: none;">
<nav>
<ul class="pagination pagination-sm r-m0">
<li class="{{if (eq launchConfigIndex -1) 'active'}}"><a href="#" {{action "selectLaunchConfig" -1}}>{{default-str service.name default="(Primary Service)"}}</a></li>
{{#each service.secondaryLaunchConfigs as |slc index|}}
<li class="{{if (eq launchConfigIndex index) 'active'}}"><a href="#" {{action "selectLaunchConfig" index}}>{{default-str slc.name (concat-str '(Sidekick #' (one-louder index) ')')}}</a></li>
{{/each}}
<li><a {{action "addSidekick"}} tooltip="Add Sidekick"><i class="icon icon-plus"/></a></li>
{{#unless isUpgrade}}
<li class="hand"><a {{action "addSidekick"}}><i class="icon icon-plus"/> Add Sidekick</a></li>
{{/unless}}
</ul>
</nav>
</div>
</span>
</div>
{{/if}}
{{/if}}
@ -51,7 +55,12 @@
<div data-launchindex="-1">
<section class="horizontal-form container-fluid">
{{form-name-description model=primaryResource namePlaceholder="e.g. myapp" descriptionPlaceholder="My application"}}
{{form-name-description
model=primaryResource
namePlaceholder="e.g. myapp"
nameDisabled=isUpgrade
descriptionPlaceholder="My application"
}}
{{form-divider}}
@ -61,7 +70,7 @@
{{form-ports initialPorts=launchConfig.ports errors=portErrors portsAsStrArray=launchConfig.ports}}
{{#if isService}}
{{#if (and isService (not isSidekick))}}
{{form-divider}}
{{form-service-links
@ -150,6 +159,7 @@
{{new-container
isService=true
isSidekick=true
isUpgrade=isUpgrade
launchConfig=slc
service=slc
primaryResource=slc
@ -160,12 +170,10 @@
{{/each}}
{{/if}}
{{#unless isSidekick}}
{{#save-cancel save="save" cancel="cancel"}}
{{#if isService}}
<button class="btn btn-default" {{action "addSidekick"}}>Add a Sidekick</button>
{{/if}}
{{#if (not-eq launchConfigIndex -1)}}
{{#save-cancel createLabel=(if isUpgrade 'Upgrade' 'Create') save="save" cancel="cancel"}}
{{#if (and (not isUpgrade) (not-eq launchConfigIndex -1))}}
<button class="btn btn-default" {{action "removeSidekick"}}>Remove this Sidekick</button>
{{/if}}
{{/save-cancel}}

View File

@ -4,7 +4,7 @@
</div>
<div class="pod-state {{stateBackground}}"><span>{{model.displayState}}</span></div>
<div class="pod-icon"><i class="icon icon-lg {{model.stateIcon}} {{iconColor}}"></i></div>
<div class="pod-icon"><i class="{{model.stateIcon}} icon-lg fa-lg {{iconColor}}"></i></div>
<div class="pod-name">
{{#link-to "service" model.environmentId model.id}}{{model.displayName}}{{/link-to}}
{{#if model.showTransitioningMessage}}

View File

@ -113,12 +113,16 @@ export default Ember.Mixin.create({
isDeleted: Ember.computed.equal('state','removed'),
isPurged: Ember.computed.equal('state','purged'),
relevantState: function() {
return this.get('combinedState') || this.get('state');
}.property('combinedState','state'),
displayState: function() {
var state = this.get('state')||'';
var state = this.get('relevantState')||'';
return state.split(/-/).map((word) => {
return Util.ucFirst(word);
}).join('-');
}.property('state'),
}.property('relevantState'),
showTransitioningMessage: function() {
var trans = this.get('transitioning');
@ -127,42 +131,63 @@ export default Ember.Mixin.create({
stateIcon: function() {
var trans = this.get('transitioning');
var icon = '';
if ( trans === 'yes' )
{
return 'icon icon-spinner icon-spin';
icon = 'icon icon-spinner icon-spin';
}
else if ( trans === 'error' )
{
return 'icon icon-alert text-danger';
icon = 'icon icon-alert text-danger';
}
else
{
var map = this.constructor.stateMap;
var key = (this.get('state')||'').toLowerCase();
var key = (this.get('relevantState')||'').toLowerCase();
if ( map && map[key] && map[key].icon !== undefined)
{
if ( typeof map[key].icon === 'function' )
{
return map[key].icon(this);
icon = map[key].icon(this);
}
else
{
return map[key].icon;
icon = map[key].icon;
}
}
if ( defaultStateMap[key] && defaultStateMap[key].icon )
if ( !icon && defaultStateMap[key] && defaultStateMap[key].icon )
{
return defaultStateMap[key].icon;
icon = defaultStateMap[key].icon;
}
return this.constructor.defaultStateIcon;
if ( !icon )
{
icon = this.constructor.defaultStateIcon;
}
if ( icon.indexOf('fa-') >= 0 )
{
if ( icon.indexOf('fa ') === -1 )
{
icon = 'fa ' + icon;
}
}
else
{
if ( icon.indexOf('icon ') === -1 )
{
icon = 'icon ' + icon;
}
}
}
}.property('state','transitioning'),
return icon;
}.property('relevantState','transitioning'),
stateColor: function() {
var map = this.constructor.stateMap;
var key = (this.get('state')||'').toLowerCase();
var key = (this.get('relevantState')||'').toLowerCase();
if ( map && map[key] && map[key].color !== undefined )
{
if ( typeof map[key].color === 'function' )
@ -181,7 +206,7 @@ export default Ember.Mixin.create({
}
return this.constructor.defaultStateColor;
}.property('state','transitioning'),
}.property('relevantState','transitioning'),
stateBackground: function() {
return this.get('stateColor').replace("text-","bg-");

View File

@ -13,32 +13,31 @@ var Service = Resource.extend(ReadLabels, {
type: 'service',
actions: {
activate: function() {
activate() {
return this.doAction('activate');
},
deactivate: function() {
deactivate() {
return this.doAction('deactivate');
},
cancelUpgrade: function() {
cancelUpgrade() {
return this.doAction('cancelupgrade');
},
cancelRollback: function() {
cancelRollback() {
return this.doAction('cancelrollback');
},
finishUpgrade: function() {
finishUpgrade() {
return this.doAction('finishupgrade');
},
rollback: function() {
rollback() {
return this.doAction('rollback');
},
edit: function() {
edit() {
var type = this.get('type').toLowerCase();
if ( type === 'loadbalancerservice' )
{
@ -72,12 +71,12 @@ var Service = Resource.extend(ReadLabels, {
}
},
scaleUp: function() {
scaleUp() {
this.incrementProperty('scale');
this.saveScale();
},
scaleDown: function() {
scaleDown() {
if ( this.get('scale') >= 1 )
{
this.decrementProperty('scale');
@ -85,7 +84,15 @@ var Service = Resource.extend(ReadLabels, {
}
},
clone: function() {
upgrade() {
this.get('application').transitionToRoute('service.new', {queryParams: {
serviceId: this.get('id'),
upgrade: true,
environmentId: this.get('environmentId'),
}});
},
clone() {
var route;
switch ( this.get('type').toLowerCase() )
{
@ -104,7 +111,7 @@ var Service = Resource.extend(ReadLabels, {
},
scaleTimer: null,
saveScale: function() {
saveScale() {
if ( this.get('scaleTimer') )
{
Ember.run.cancel(this.get('scaleTimer'));
@ -121,19 +128,20 @@ var Service = Resource.extend(ReadLabels, {
var a = this.get('actionLinks');
var choices = [
{ label: 'Start', icon: 'icon icon-play', action: 'activate', enabled: !!a.activate, color: 'text-success'},
{ label: 'Stop', icon: 'icon icon-pause', action: 'deactivate', enabled: !!a.deactivate, color: 'text-danger'},
{ label: 'Delete', icon: 'icon icon-trash', action: 'promptDelete', enabled: !!a.remove, altAction: 'delete', color: 'text-warning' },
{ label: 'Purge', icon: '', action: 'purge', enabled: !!a.purge },
{ label: 'Start', icon: 'icon icon-play', action: 'activate', enabled: !!a.activate, color: 'text-success'},
{ label: 'Stop', icon: 'icon icon-pause', action: 'deactivate', enabled: !!a.deactivate, color: 'text-danger'},
{ label: 'Delete', icon: 'icon icon-trash', action: 'promptDelete', enabled: !!a.remove, altAction: 'delete', color: 'text-warning' },
{ label: 'Purge', icon: '', action: 'purge', enabled: !!a.purge },
{ divider: true },
{ label: 'Finish Upgrade', icon: 'fa fa-thumbs-o-up', action: 'finishUpgrade', enabled: !!a.finishupgrade },
{ label: 'Cancel Upgrade', icon: 'fa fa-life-ring', action: 'cancelUpgrade', enabled: !!a.cancelupgrade },
{ label: 'Rollback', icon: 'fa fa-history', action: 'rollback', enabled: !!a.rollback },
{ label: 'Cancel Rollback', icon: 'fa fa-life-ring', action: 'cancelRollback', enabled: !!a.cancelrollback },
{ label: 'Upgrade', icon: 'fa fa-arrow-circle-o-up',action: 'upgrade', enabled: !!a.upgrade },
{ label: 'Finish Upgrade', icon: 'fa fa-thumbs-o-up', action: 'finishUpgrade', enabled: !!a.finishupgrade },
{ label: 'Cancel Upgrade', icon: 'fa fa-life-ring', action: 'cancelUpgrade', enabled: !!a.cancelupgrade },
{ label: 'Rollback', icon: 'fa fa-history', action: 'rollback', enabled: !!a.rollback },
{ label: 'Cancel Rollback', icon: 'fa fa-life-ring', action: 'cancelRollback', enabled: !!a.cancelrollback },
{ divider: true },
{ label: 'View in API', icon: 'icon icon-externallink', action: 'goToApi', enabled: true },
{ label: 'Clone', icon: 'icon icon-copy', action: 'clone', enabled: true },
{ label: 'Edit', icon: 'icon icon-edit', action: 'edit', enabled: !!a.update },
{ label: 'View in API', icon: 'icon icon-externallink', action: 'goToApi', enabled: true },
{ label: 'Clone', icon: 'icon icon-copy', action: 'clone', enabled: true },
{ label: 'Edit', icon: 'icon icon-edit', action: 'edit', enabled: !!a.update },
];
return choices;
@ -356,9 +364,15 @@ Service.reopenClass({
},
stateMap: {
'active': {icon: activeIcon, color: 'text-success'},
'upgrading': {icon: 'icon-arrow-up', color: 'text-info'},
'canceling-upgrade':{icon: 'icon-arrow-down', color: 'text-info'},
'active': {icon: activeIcon, color: 'text-success'},
'canceled-rollback': {icon: 'fa fa-life-ring', color: 'text-info'},
'canceled-upgrade': {icon: 'fa fa-life-ring', color: 'text-info'},
'canceling-rollback': {icon: 'fa fa-life-ring', color: 'text-info'},
'canceling-upgrade': {icon: 'fa fa-life-ring', color: 'text-info'},
'finishing-upgrade': {icon: 'fa fa-arrow-circle-o-up', color: 'text-info'},
'rolling-back': {icon: 'fa fa-history', color: 'text-info'},
'upgraded': {icon: 'fa fa-arrow-circle-o-up', color: 'text-info'},
'upgrading': {icon: 'fa fa-arrow-circle-o-up', color: 'text-info'},
}
});

View File

@ -1,10 +1,11 @@
import Ember from 'ember';
export default Ember.Controller.extend({
queryParams: ['environmentId','serviceId','containerId'],
queryParams: ['environmentId','serviceId','containerId','upgrade'],
environmentId: null,
serviceId: null,
containerId: null,
upgrade: null,
actions: {
done() {

View File

@ -28,6 +28,15 @@ export default Ember.Route.extend({
var serviceLinks = [];
var secondaryLaunchConfigs = [];
if ( params.upgrade )
{
return Ember.Object.create({
service: serviceOrContainer.clone(),
allHosts: allHosts,
allServices: allServices,
});
}
var instanceData, serviceData, healthCheckData;
if ( serviceOrContainer )
{
@ -100,6 +109,7 @@ export default Ember.Route.extend({
controller.set('environmentId', null);
controller.set('serviceId', null);
controller.set('containerId', null);
controller.set('upgrade', null);
}
}
});

View File

@ -1,5 +1,6 @@
{{new-container
isService=true
isUpgrade=upgrade
isSidekick=false
launchConfig=model.service.launchConfig
service=model.service