mirror of https://github.com/portainer/portainer
* fix(services): replicas numbers display is now correct with constraints and down nodes * refactor(helpers): constraint helper has less complexity * feat(services): constraints on node/engine labels are now supported * refactor(helpers): ConstraintsHelper - remove regex patterns and improve code lisibility * refactor(helpers): rework matchesConstraint() for better code lisibility and lodash find() instead for IE compatibilitypull/2193/head
parent
e1e263d8c8
commit
1b51daf9c4
|
@ -96,7 +96,7 @@
|
||||||
<td>{{ item.Image | hideshasum }}</td>
|
<td>{{ item.Image | hideshasum }}</td>
|
||||||
<td ng-controller="ServicesDatatableActionsController as actionCtrl">
|
<td ng-controller="ServicesDatatableActionsController as actionCtrl">
|
||||||
{{ item.Mode }}
|
{{ item.Mode }}
|
||||||
<code>{{ item.Tasks | runningtaskscount }}</code> / <code>{{ item.Mode === 'replicated' ? item.Replicas : ($ctrl.nodes | availablenodecount) }}</code>
|
<code>{{ item.Tasks | runningtaskscount }}</code> / <code>{{ item.Mode === 'replicated' ? item.Replicas : ($ctrl.nodes | availablenodecount:item) }}</code>
|
||||||
<span ng-if="item.Mode === 'replicated' && !item.Scale">
|
<span ng-if="item.Mode === 'replicated' && !item.Scale">
|
||||||
<a class="interactive" ng-click="item.Scale = true; item.ReplicaCount = item.Replicas; $event.stopPropagation();">
|
<a class="interactive" ng-click="item.Scale = true; item.ReplicaCount = item.Replicas; $event.stopPropagation();">
|
||||||
<i class="fa fa-arrows-alt-v" aria-hidden="true"></i> Scale
|
<i class="fa fa-arrows-alt-v" aria-hidden="true"></i> Scale
|
||||||
|
|
|
@ -192,26 +192,26 @@ angular.module('portainer.docker')
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter('availablenodecount', function () {
|
.filter('availablenodecount', ['ConstraintsHelper', function (ConstraintsHelper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
return function (nodes) {
|
return function (nodes, service) {
|
||||||
var availableNodes = 0;
|
var availableNodes = 0;
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
var node = nodes[i];
|
var node = nodes[i];
|
||||||
if (node.Availability === 'active' && node.Status === 'ready') {
|
if (node.Availability === 'active' && node.Status === 'ready' && ConstraintsHelper.matchesServiceConstraints(service, node)) {
|
||||||
availableNodes++;
|
availableNodes++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return availableNodes;
|
return availableNodes;
|
||||||
};
|
};
|
||||||
})
|
}])
|
||||||
.filter('runningtaskscount', function () {
|
.filter('runningtaskscount', function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
return function (tasks) {
|
return function (tasks) {
|
||||||
var runningTasks = 0;
|
var runningTasks = 0;
|
||||||
for (var i = 0; i < tasks.length; i++) {
|
for (var i = 0; i < tasks.length; i++) {
|
||||||
var task = tasks[i];
|
var task = tasks[i];
|
||||||
if (task.Status.State === 'running') {
|
if (task.Status.State === 'running' && task.DesiredState === 'running') {
|
||||||
runningTasks++;
|
runningTasks++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
function ConstraintModel(op, key, value) {
|
||||||
|
this.op = op;
|
||||||
|
this.value = value;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
var patterns = {
|
||||||
|
id: {
|
||||||
|
nodeId: 'node.id',
|
||||||
|
nodeHostname: 'node.hostname',
|
||||||
|
nodeRole: 'node.role',
|
||||||
|
nodeLabels: 'node.labels.',
|
||||||
|
engineLabels: 'engine.labels.'
|
||||||
|
},
|
||||||
|
op: {
|
||||||
|
eq: '==',
|
||||||
|
neq: '!='
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function matchesConstraint(value, constraint) {
|
||||||
|
if (!constraint ||
|
||||||
|
(constraint.op === patterns.op.eq && value === constraint.value) ||
|
||||||
|
(constraint.op === patterns.op.neq && value !== constraint.value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesLabel(labels, constraint) {
|
||||||
|
if (!constraint) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var found = _.find(labels, function (label) {
|
||||||
|
return label.key === constraint.key && label.value === constraint.value;
|
||||||
|
});
|
||||||
|
return found !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractValue(constraint, op) {
|
||||||
|
return constraint.split(op).pop().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractCustomLabelKey(constraint, op, baseLabelKey) {
|
||||||
|
return constraint.split(op).shift().trim().replace(baseLabelKey, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('portainer.docker')
|
||||||
|
.factory('ConstraintsHelper', [function ConstraintsHelperFactory() {
|
||||||
|
'use strict';
|
||||||
|
return {
|
||||||
|
transformConstraints: function (constraints) {
|
||||||
|
var transform = {};
|
||||||
|
for (var i = 0; i < constraints.length; i++) {
|
||||||
|
var constraint = constraints[i];
|
||||||
|
|
||||||
|
var op;
|
||||||
|
if (constraint.includes(patterns.op.eq)) {
|
||||||
|
op = patterns.op.eq;
|
||||||
|
} else if (constraint.includes(patterns.op.neq)) {
|
||||||
|
op = patterns.op.neq;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = extractValue(constraint, op);
|
||||||
|
var key = '';
|
||||||
|
switch (true) {
|
||||||
|
case constraint.includes(patterns.id.nodeId):
|
||||||
|
transform.nodeId = new ConstraintModel(op, key, value);
|
||||||
|
break;
|
||||||
|
case constraint.includes(patterns.id.nodeHostname):
|
||||||
|
transform.nodeHostname = new ConstraintModel(op, key, value);
|
||||||
|
break;
|
||||||
|
case constraint.includes(patterns.id.nodeRole):
|
||||||
|
transform.nodeRole = new ConstraintModel(op, key, value);
|
||||||
|
break;
|
||||||
|
case constraint.includes(patterns.id.nodeLabels):
|
||||||
|
key = extractCustomLabelKey(constraint, op, patterns.id.nodeLabels);
|
||||||
|
transform.nodeLabels = new ConstraintModel(op, key, value);
|
||||||
|
break;
|
||||||
|
case constraint.includes(patterns.id.engineLabels):
|
||||||
|
key = extractCustomLabelKey(constraint, op, patterns.id.engineLabels);
|
||||||
|
transform.engineLabels = new ConstraintModel(op, key, value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transform;
|
||||||
|
},
|
||||||
|
matchesServiceConstraints: function (service, node) {
|
||||||
|
if (service.Constraints === undefined || service.Constraints.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var constraints = this.transformConstraints(angular.copy(service.Constraints));
|
||||||
|
if (matchesConstraint(node.Id, constraints.nodeId) &&
|
||||||
|
matchesConstraint(node.Hostname, constraints.nodeHostname) &&
|
||||||
|
matchesConstraint(node.Role, constraints.nodeRole) &&
|
||||||
|
matchesLabel(node.Labels, constraints.nodeLabels) &&
|
||||||
|
matchesLabel(node.EngineLabels, constraints.engineLabels)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -5,6 +5,7 @@ function TaskViewModel(data) {
|
||||||
this.Slot = data.Slot;
|
this.Slot = data.Slot;
|
||||||
this.Spec = data.Spec;
|
this.Spec = data.Spec;
|
||||||
this.Status = data.Status;
|
this.Status = data.Status;
|
||||||
|
this.DesiredState = data.DesiredState;
|
||||||
this.ServiceId = data.ServiceID;
|
this.ServiceId = data.ServiceID;
|
||||||
this.NodeId = data.NodeID;
|
this.NodeId = data.NodeID;
|
||||||
if (data.Status && data.Status.ContainerStatus && data.Status.ContainerStatus.ContainerID) {
|
if (data.Status && data.Status.ContainerStatus && data.Status.ContainerStatus.ContainerID) {
|
||||||
|
|
Loading…
Reference in New Issue