ui/app/models/workload.js

451 lines
14 KiB
JavaScript

import { later, cancel } from '@ember/runloop';
import { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import Resource from 'ember-api-store/models/resource';
import C from 'ui/utils/constants';
import Util from 'ui/utils/util';
import { denormalizeId, denormalizeIdArray } from 'ember-api-store/utils/denormalize';
import StateCounts from 'ui/mixins/state-counts';
import EndpointPorts from 'ui/mixins/endpoint-ports';
import { inject as service } from "@ember/service";
var Workload = Resource.extend(StateCounts, EndpointPorts, {
intl: service(),
growl: service(),
modalService: service('modal'),
allServices: service(),
scope: service(),
router: service(),
namespaceObj: denormalizeId('namespace','namespace'),
instances: denormalizeIdArray('instanceIds'),
instanceCount: alias('instances.length'),
stack: denormalizeId('stackId'),
init() {
this._super(...arguments);
this.defineStateCounts('instances', 'instanceStates', 'instanceCountSort');
},
lcType: computed('type', function() {
return (this.get('type')||'').toLowerCase();
}),
actions: {
editDns() {
this.get('modalService').toggleModal('modal-edit-dns', this);
},
activate() {
return this.doAction('activate');
},
deactivate() {
return this.doAction('deactivate');
},
pause() {
return this.doAction('pause');
},
restart() {
return this.doAction('restart', {rollingRestartStrategy: {}});
},
rollback() {
this.get('modalService').toggleModal('modal-rollback-service', {
originalModel: this
});
},
garbageCollect() {
return this.doAction('garbagecollect');
},
// Start and stop are only here to mimic the same actions that exist on a container
// the reason being bulkActions, to forgo writing distinct logic for containers vs
// services lets just mimic the actions here.
start() {
return this.doAction('activate');
},
stop() {
return this.doAction('deactivate');
},
promptStop() {
this.get('modalService').toggleModal('modal-container-stop', {
model: [this]
});
},
scaleUp() {
let scale = this.get('scale');
let max = this.get('scaleMax');
scale += this.get('scaleIncrement')||1;
if ( max ) {
scale = Math.min(scale, max);
}
this.set('scale', scale);
this.saveScale();
},
scaleDown() {
let scale = this.get('scale');
let min = this.get('scaleMin') || 0;
scale -= this.get('scaleIncrement')||1;
scale = Math.max(scale, min);
this.set('scale', scale);
this.saveScale();
},
upgrade(upgradeImage='false') {
var route = 'containers.run';
if ( this.get('lcType') === 'loadbalancerservice' ) {
route = 'balancers.run';
}
this.get('router').transitionTo(route, {queryParams: {
serviceId: this.get('id'),
upgrade: true,
upgradeImage: upgradeImage,
stackId: this.get('stackId'),
}});
},
clone() {
var route;
switch ( this.get('lcType') )
{
case 'service': route = 'containers.run'; break;
case 'scalinggroup': route = 'containers.run'; break;
case 'dnsservice': route = 'dns.new'; break;
case 'loadbalancerservice': route = 'balancers.run'; break;
case 'externalservice': route = 'dns.new'; break;
default: return void this.send('error','Unknown service type: ' + this.get('type'));
}
this.get('router').transitionTo(route, {queryParams: {
serviceId: this.get('id'),
stackId: this.get('stackId'),
}});
},
addSidekick() {
this.get('router').transitionTo('containers.run', {queryParams: {
serviceId: this.get('id'),
addSidekick: true,
launchConfigIndex: (this.get('secondaryLaunchConfigs')||[]).length
}});
},
shell() {
this.get('modalService').toggleModal('modal-shell', {
model: this.get('containerForShell'),
escToClose: false,
});
},
popoutShell() {
let proj = this.get('scope.current.id');
let id = this.get('containerForShell.id');
later(() => {
window.open(`//${window.location.host}/env/${proj}/infra/console?instanceId=${id}&isPopup=true`, '_blank', "toolbars=0,width=900,height=700,left=200,top=200");
});
},
},
scaleTimer: null,
saveScale() {
if ( this.get('scaleTimer') )
{
cancel(this.get('scaleTimer'));
}
var timer = later(this, function() {
this.save().catch((err) => {
this.get('growl').fromError('Error updating scale',err);
});
}, 500);
this.set('scaleTimer', timer);
},
availableActions: function() {
let a = this.get('actionLinks');
let l = this.get('links');
let isReal = this.get('isReal');
let isK8s = this.get('isK8s');
let canHaveContainers = this.get('canHaveContainers');
let containerForShell = this.get('containerForShell');
let isDriver = ['networkdriverservice','storagedriverservice'].includes(this.get('lcType'));
let canCleanup = !!a.garbagecollect && this.get('canCleanup');
let choices = [
{ label: 'action.edit', icon: 'icon icon-edit', action: 'upgrade', enabled: !!l.update && isReal },
{ label: 'action.edit', icon: 'icon icon-pencil', action: 'editDns', enabled: !!l.update && !isReal },
{ label: 'action.rollback', icon: 'icon icon-history', action: 'rollback', enabled: !!a.rollback && isReal && !!this.get('previousRevisionId') },
{ label: 'action.garbageCollect', icon: 'icon icon-garbage', action: 'garbageCollect', enabled: canCleanup},
{ label: 'action.clone', icon: 'icon icon-copy', action: 'clone', enabled: !isK8s && !isDriver },
{ label: 'action.addSidekick', icon: 'icon icon-plus-circle', action: 'addSidekick', enabled: this.get('canHaveSidekicks') },
{ divider: true },
{ label: 'action.execute', icon: 'icon icon-terminal', action: 'shell', enabled: !!containerForShell, altAction:'popoutShell'},
// { label: 'action.logs', icon: 'icon icon-file', action: 'logs', enabled: !!a.logs, altAction: 'popoutLogs' },
{ divider: true },
{ label: 'action.pause', icon: 'icon icon-pause', action: 'pause', enabled: !!a.pause, bulkable: true},
{ label: 'action.start', icon: 'icon icon-play', action: 'start', enabled: !!a.activate, bulkable: true},
{ label: 'action.restart', icon: 'icon icon-refresh', action: 'restart', enabled: !!a.restart && canHaveContainers, bulkable: true },
{ label: 'action.stop', icon: 'icon icon-stop', action: 'promptStop', enabled: !!a.deactivate, altAction: 'stop', bulkable: true},
{ divider: true },
{ label: 'action.remove', icon: 'icon icon-trash', action: 'promptDelete', enabled: !!l.remove, altAction: 'delete', bulkable: true},
{ divider: true },
{ label: 'action.viewInApi', icon: 'icon icon-external-link', action: 'goToApi', enabled: true },
];
return choices;
}.property('actionLinks.{activate,deactivate,pause,restart,rollback,garbagecollect}','links.{update,remove}','previousRevisionId',
'lcType','isK8s','canHaveContainers','canHaveSidekicks','containerForShell'
),
image: alias('launchConfig.image'),
sortName: function() {
return Util.sortableNumericSuffix(this.get('displayName'));
}.property('displayName'),
displayStack: function() {
var stack = this.get('stack');
if ( stack ) {
return stack.get('displayName');
} else {
return '...';
}
}.property('stack.displayName'),
linkedServices: function() {
let allServices = this.get('allServices');
let stack = this.get('stack');
return (this.get('serviceLinks')||[]).map((link) => {
return {
name: link.name,
alias: link.alias,
service: allServices.matching(link.name, stack),
};
});
}.property('serviceLinks.@each.{name,alias}'),
combinedState: function() {
var service = this.get('state');
var health = this.get('healthState');
if ( service === 'active' && health ) {
// Return the health state for active services
return health;
}
// Return the service for anything else
return service;
}.property('state', 'healthState'),
isGlobalScale: function() {
return (this.get('launchConfig.labels')||{})[C.LABEL.SCHED_GLOBAL] + '' === 'true';
}.property('launchConfig.labels'),
canScaleUp: function() {
if ( !this.get('canScale') ) {
return false;
}
let scale = this.get('scale');
let max = this.get('scaleMax');
if ( !max ) {
return true;
}
scale += this.get('scaleIncrement')||1;
return scale <= max;
}.property('canScale','scaleMax','scaleIncrement','scale'),
canScaleDown: function() {
if ( !this.get('canScale') ) {
return false;
}
let scale = this.get('scale');
let min = this.get('scaleMin')||1;
scale -= this.get('scaleIncrement')||1;
return scale >= min;
}.property('canScale','scaleMin','scaleIncrement','scale'),
displayScale: function() {
if ( this.get('isGlobalScale') ) {
return this.get('intl').t('servicePage.globalScale', {scale: this.get('scale')});
} else {
return this.get('scale');
}
}.property('scale','isGlobalScale'),
canHaveContainers: function() {
if ( this.get('isReal') || this.get('isSelector') ) {
return true;
}
return [
'kubernetesservice',
'composeservice',
].includes(this.get('lcType'));
}.property('isReal','isSelector','lcType'),
isReal: function() {
if ( this.get('isSelector') ) {
return false;
}
return [
'service',
'scalinggroup',
'networkdriverservice',
'storagedriverservice',
'loadbalancerservice',
].includes(this.get('lcType'));
}.property('lcType','isSelector'),
canHaveSidekicks: function() {
return ['service','scalinggroup'].includes(this.get('lcType'));
}.property('lcType'),
hasPorts: alias('isReal'),
hasImage: alias('isReal'),
canUpgrade: alias('isReal'),
canHaveLabels: alias('isReal'),
canScale: alias('isReal'),
realButNotLb: function() {
return this.get('isReal') && !this.get('isBalancer');
}.property('isReal','isBalancer'),
canHaveLinks: alias('realButNotLb'),
canChangeNetworking: alias('realButNotLb'),
canChangeSecurity: alias('realButNotLb'),
canHaveSecrets: alias('realButNotLb'),
canHaveEnvironment: alias('realButNotLb'),
canHaveHealthCheck: function() {
return [
'service',
'scalinggroup',
'externalservice',
].includes(this.get('lcType'));
}.property('lcType'),
isSelector: function() {
return !!this.get('selector');
}.property('selector'),
isBalancer: function() {
return ['loadbalancerservice'].indexOf(this.get('lcType')) >= 0;
}.property('lcType'),
canBalanceTo: function() {
if ( this.get('lcType') === 'externalservice' && this.get('hostname') !== null) {
return false;
}
return true;
}.property('lcType','hostname'),
isK8s: function() {
return ['kubernetesservice'].indexOf(this.get('lcType')) >= 0;
}.property('lcType'),
displayType: function() {
let known = [
'loadbalancerservice',
'dnsservice',
'externalservice',
'kubernetesservice',
'composeservice',
'networkdriverservice',
'selectorservice',
'storagedriverservice',
'service'
];
let type = this.get('lcType');
if ( this.get('isSelector') ) {
type = 'selectorservice';
}
if ( !known.includes(type) ) {
type = 'service';
}
if ( type === 'externalservice' ) {
if ( this.get('hostname') ) {
type += '-host';
} else {
type += '-ip';
}
}
return this.get('intl').t('servicePage.type.'+ type);
}.property('lcType','isSelector','intl.locale'),
hasSidekicks: function() {
return this.get('secondaryLaunchConfigs.length') > 0;
}.property('secondaryLaunchConfigs.length'),
activeIcon: function() {
return activeIcon(this);
}.property('lcType'),
memoryReservationBlurb: computed('launchConfig.memoryReservation', function() {
if ( this.get('launchConfig.memoryReservation') ) {
return Util.formatSi(this.get('launchConfig.memoryReservation'), 1024, 'iB', 'B');
}
}),
containerForShell: function() {
return this.get('instances').findBy('combinedState','running');
}.property('instances.@each.combinedState'),
canCleanup: function() {
return !!this.get('instances').findBy('desired',false);
}.property('instances.@each.desired'),
});
export function activeIcon(service)
{
var out = 'icon icon-services';
switch ( service.get('lcType') )
{
case 'loadbalancerservice': out = 'icon icon-fork'; break;
case 'dnsservice': out = 'icon icon-compass'; break;
case 'externalservice': out = 'icon icon-cloud'; break;
case 'kubernetesservice': out = 'icon icon-kubernetes'; break;
case 'composeservice': out = 'icon icon-docker'; break;
}
return out;
}
Workload.reopenClass({
stateMap: {
'active': {icon: activeIcon, color: 'text-success'},
'canceled-rollback': {icon: 'icon icon-life-ring', color: 'text-info'},
'canceled-upgrade': {icon: 'icon icon-life-ring', color: 'text-info'},
'canceling-rollback': {icon: 'icon icon-life-ring', color: 'text-info'},
'canceling-upgrade': {icon: 'icon icon-life-ring', color: 'text-info'},
'finishing-upgrade': {icon: 'icon icon-arrow-circle-up', color: 'text-info'},
'rolling-back': {icon: 'icon icon-history', color: 'text-info'},
'upgraded': {icon: 'icon icon-arrow-circle-up', color: 'text-info'},
'upgrading': {icon: 'icon icon-arrow-circle-up', color: 'text-info'},
}
});
export default Workload;