mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(services): add support for placement preferences (#1003)
This commit is contained in:
parent
8dc6d05ed6
commit
25ed6a71fb
12 changed files with 254 additions and 144 deletions
|
@ -1,6 +1,6 @@
|
||||||
angular.module('createNetwork', [])
|
angular.module('createNetwork', [])
|
||||||
.controller('CreateNetworkController', ['$scope', '$state', 'Notifications', 'Network',
|
.controller('CreateNetworkController', ['$scope', '$state', 'Notifications', 'Network', 'LabelHelper',
|
||||||
function ($scope, $state, Notifications, Network) {
|
function ($scope, $state, Notifications, Network, LabelHelper) {
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
DriverOptions: [],
|
DriverOptions: [],
|
||||||
Subnet: '',
|
Subnet: '',
|
||||||
|
@ -30,7 +30,7 @@ function ($scope, $state, Notifications, Network) {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addLabel = function() {
|
$scope.addLabel = function() {
|
||||||
$scope.formValues.Labels.push({ name: '', value: ''});
|
$scope.formValues.Labels.push({ key: '', value: ''});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeLabel = function(index) {
|
$scope.removeLabel = function(index) {
|
||||||
|
@ -74,13 +74,7 @@ function ($scope, $state, Notifications, Network) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareLabelsConfig(config) {
|
function prepareLabelsConfig(config) {
|
||||||
var labels = {};
|
config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels);
|
||||||
$scope.formValues.Labels.forEach(function (label) {
|
|
||||||
if (label.name && label.value) {
|
|
||||||
labels[label.name] = label.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
config.Labels = labels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareConfiguration() {
|
function prepareConfiguration() {
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px;">
|
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px;">
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">name</span>
|
<span class="input-group-addon">name</span>
|
||||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo">
|
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">value</span>
|
<span class="input-group-addon">value</span>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
angular.module('createSecret', [])
|
angular.module('createSecret', [])
|
||||||
.controller('CreateSecretController', ['$scope', '$state', 'Notifications', 'SecretService',
|
.controller('CreateSecretController', ['$scope', '$state', 'Notifications', 'SecretService', 'LabelHelper',
|
||||||
function ($scope, $state, Notifications, SecretService) {
|
function ($scope, $state, Notifications, SecretService, LabelHelper) {
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
Name: '',
|
Name: '',
|
||||||
Data: '',
|
Data: '',
|
||||||
|
@ -9,7 +9,7 @@ function ($scope, $state, Notifications, SecretService) {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addLabel = function() {
|
$scope.addLabel = function() {
|
||||||
$scope.formValues.Labels.push({ name: '', value: ''});
|
$scope.formValues.Labels.push({ key: '', value: ''});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeLabel = function(index) {
|
$scope.removeLabel = function(index) {
|
||||||
|
@ -17,13 +17,7 @@ function ($scope, $state, Notifications, SecretService) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function prepareLabelsConfig(config) {
|
function prepareLabelsConfig(config) {
|
||||||
var labels = {};
|
config.Labels = LabelHelper.fromKeyValueToLabelHash($scope.formValues.Labels);
|
||||||
$scope.formValues.Labels.forEach(function (label) {
|
|
||||||
if (label.name && label.value) {
|
|
||||||
labels[label.name] = label.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
config.Labels = labels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareSecretData(config) {
|
function prepareSecretData(config) {
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px;">
|
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px;">
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">name</span>
|
<span class="input-group-addon">name</span>
|
||||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo">
|
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">value</span>
|
<span class="input-group-addon">value</span>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||||
// See app/components/templates/templatesController.js as a reference.
|
// See app/components/templates/templatesController.js as a reference.
|
||||||
angular.module('createService', [])
|
angular.module('createService', [])
|
||||||
.controller('CreateServiceController', ['$q', '$scope', '$state', 'Service', 'ServiceHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'ControllerDataPipeline', 'FormValidator', 'RegistryService', 'HttpRequestHelper',
|
.controller('CreateServiceController', ['$q', '$scope', '$state', 'Service', 'ServiceHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'LabelHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'ControllerDataPipeline', 'FormValidator', 'RegistryService', 'HttpRequestHelper',
|
||||||
function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, Authentication, ResourceControlService, Notifications, ControllerDataPipeline, FormValidator, RegistryService, HttpRequestHelper) {
|
function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, LabelHelper, Authentication, ResourceControlService, Notifications, ControllerDataPipeline, FormValidator, RegistryService, HttpRequestHelper) {
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
Name: '',
|
Name: '',
|
||||||
|
@ -23,6 +23,7 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
|
||||||
Ports: [],
|
Ports: [],
|
||||||
Parallelism: 1,
|
Parallelism: 1,
|
||||||
PlacementConstraints: [],
|
PlacementConstraints: [],
|
||||||
|
PlacementPreferences: [],
|
||||||
UpdateDelay: 0,
|
UpdateDelay: 0,
|
||||||
FailureAction: 'pause',
|
FailureAction: 'pause',
|
||||||
Secrets: []
|
Secrets: []
|
||||||
|
@ -81,7 +82,7 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addPlacementPreference = function() {
|
$scope.addPlacementPreference = function() {
|
||||||
$scope.formValues.PlacementPreferences.push({ key: '', operator: '==', value: '' });
|
$scope.formValues.PlacementPreferences.push({ strategy: 'spread', value: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removePlacementPreference = function(index) {
|
$scope.removePlacementPreference = function(index) {
|
||||||
|
@ -89,7 +90,7 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addLabel = function() {
|
$scope.addLabel = function() {
|
||||||
$scope.formValues.Labels.push({ name: '', value: ''});
|
$scope.formValues.Labels.push({ key: '', value: ''});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeLabel = function(index) {
|
$scope.removeLabel = function(index) {
|
||||||
|
@ -97,7 +98,7 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addContainerLabel = function() {
|
$scope.addContainerLabel = function() {
|
||||||
$scope.formValues.ContainerLabels.push({ name: '', value: ''});
|
$scope.formValues.ContainerLabels.push({ key: '', value: ''});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeContainerLabel = function(index) {
|
$scope.removeContainerLabel = function(index) {
|
||||||
|
@ -170,21 +171,8 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareLabelsConfig(config, input) {
|
function prepareLabelsConfig(config, input) {
|
||||||
var labels = {};
|
config.Labels = LabelHelper.fromKeyValueToLabelHash(input.Labels);
|
||||||
input.Labels.forEach(function (label) {
|
config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(input.ContainerLabels);
|
||||||
if (label.name && label.value) {
|
|
||||||
labels[label.name] = label.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
config.Labels = labels;
|
|
||||||
|
|
||||||
var containerLabels = {};
|
|
||||||
input.ContainerLabels.forEach(function (label) {
|
|
||||||
if (label.name && label.value) {
|
|
||||||
containerLabels[label.name] = label.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
config.TaskTemplate.ContainerSpec.Labels = containerLabels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareVolumes(config, input) {
|
function prepareVolumes(config, input) {
|
||||||
|
@ -213,8 +201,10 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
|
||||||
FailureAction: input.FailureAction
|
FailureAction: input.FailureAction
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function preparePlacementConfig(config, input) {
|
function preparePlacementConfig(config, input) {
|
||||||
config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(input.PlacementConstraints);
|
config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(input.PlacementConstraints);
|
||||||
|
config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(input.PlacementPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareSecretConfig(config, input) {
|
function prepareSecretConfig(config, input) {
|
||||||
|
|
|
@ -328,7 +328,7 @@
|
||||||
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px;">
|
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px;">
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">name</span>
|
<span class="input-group-addon">name</span>
|
||||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo">
|
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">value</span>
|
<span class="input-group-addon">value</span>
|
||||||
|
@ -355,7 +355,7 @@
|
||||||
<div ng-repeat="label in formValues.ContainerLabels" style="margin-top: 2px;">
|
<div ng-repeat="label in formValues.ContainerLabels" style="margin-top: 2px;">
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">name</span>
|
<span class="input-group-addon">name</span>
|
||||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo">
|
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">value</span>
|
<span class="input-group-addon">value</span>
|
||||||
|
|
|
@ -29,3 +29,29 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<form class="form-horizontal" style="margin-top: 15px;" ng-if="applicationState.endpoint.apiVersion >= 1.30">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12" style="margin-top: 5px;">
|
||||||
|
<label class="control-label text-left">Placement preferences</label>
|
||||||
|
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addPlacementPreference()">
|
||||||
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> placement preference
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||||
|
<div ng-repeat="preference in formValues.PlacementPreferences" style="margin-top: 2px;">
|
||||||
|
<div class="input-group col-sm-4 input-group-sm">
|
||||||
|
<span class="input-group-addon">strategy</span>
|
||||||
|
<input type="text" class="form-control" ng-model="preference.strategy" placeholder="e.g. spread">
|
||||||
|
</div>
|
||||||
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
|
<span class="input-group-addon">value</span>
|
||||||
|
<input type="text" class="form-control" ng-model="preference.value" placeholder="e.g. node.labels.datacenter">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-danger" type="button" ng-click="removePlacementPreference($index)">
|
||||||
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
57
app/components/service/includes/placementPreferences.html
Normal file
57
app/components/service/includes/placementPreferences.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<div ng-if="service.ServicePreferences && applicationState.endpoint.apiVersion >= 1.30" id="service-placement-preferences">
|
||||||
|
<rd-widget>
|
||||||
|
<rd-widget-header icon="fa-tasks" title="Placement preferences">
|
||||||
|
<div class="nopadding">
|
||||||
|
<a class="btn btn-default btn-sm pull-right" ng-click="isUpdating || addPlacementPreference(service)" ng-disabled="isUpdating">
|
||||||
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> placement preference
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</rd-widget-header>
|
||||||
|
<rd-widget-body ng-if="service.ServicePreferences.length === 0">
|
||||||
|
<p>There are no placement preferences for this service.</p>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget-body ng-if="service.ServicePreferences.length > 0" classes="no-padding">
|
||||||
|
<table class="table" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Strategy</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="preference in service.ServicePreferences">
|
||||||
|
<td>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" class="form-control" ng-model="preference.strategy" placeholder="e.g. node.role" ng-change="updatePlacementPreference(service, preference)" ng-disabled="isUpdating">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" class="form-control" ng-model="preference.value" placeholder="e.g. manager" ng-change="updatePlacementPreference(service, preference)" ng-disabled="isUpdating">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-sm btn-danger" type="button" ng-click="removePlacementPreference(service, $index)" ng-disabled="isUpdating">
|
||||||
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget-footer>
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!hasChanges(service, ['ServicePreferences'])" ng-click="updateService(service)">Apply changes</button>
|
||||||
|
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a ng-click="cancelChanges(service, ['ServicePreferences'])">Reset changes</a></li>
|
||||||
|
<li><a ng-click="cancelChanges(service)">Reset all changes</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</rd-widget-footer>
|
||||||
|
</rd-widget>
|
||||||
|
</div>
|
|
@ -113,6 +113,7 @@
|
||||||
<li><a href ng-click="goToItem('service-network-specs')">Network & published ports</a></li>
|
<li><a href ng-click="goToItem('service-network-specs')">Network & published ports</a></li>
|
||||||
<li><a href ng-click="goToItem('service-resources')">Resource limits & reservations</a></li>
|
<li><a href ng-click="goToItem('service-resources')">Resource limits & reservations</a></li>
|
||||||
<li><a href ng-click="goToItem('service-placement-constraints')">Placement constraints</a></li>
|
<li><a href ng-click="goToItem('service-placement-constraints')">Placement constraints</a></li>
|
||||||
|
<li><a href ng-click="goToItem('service-placement-preferences')" ng-if="applicationState.endpoint.apiVersion >= 1.30">Placement preferences</a></li>
|
||||||
<li><a href ng-click="goToItem('service-restart-policy')">Restart policy</a></li>
|
<li><a href ng-click="goToItem('service-restart-policy')">Restart policy</a></li>
|
||||||
<li><a href ng-click="goToItem('service-update-config')">Update configuration</a></li>
|
<li><a href ng-click="goToItem('service-update-config')">Update configuration</a></li>
|
||||||
<li><a href ng-click="goToItem('service-labels')">Service labels</a></li>
|
<li><a href ng-click="goToItem('service-labels')">Service labels</a></li>
|
||||||
|
@ -152,6 +153,7 @@
|
||||||
<h3 id="service-specs">Service specification</h3>
|
<h3 id="service-specs">Service specification</h3>
|
||||||
<div id="service-resources" class="padding-top" ng-include="'app/components/service/includes/resources.html'"></div>
|
<div id="service-resources" class="padding-top" ng-include="'app/components/service/includes/resources.html'"></div>
|
||||||
<div id="service-placement-constraints" class="padding-top" ng-include="'app/components/service/includes/constraints.html'"></div>
|
<div id="service-placement-constraints" class="padding-top" ng-include="'app/components/service/includes/constraints.html'"></div>
|
||||||
|
<div id="service-placement-preferences" class="padding-top" ng-include="'app/components/service/includes/placementPreferences.html'"></div>
|
||||||
<div id="service-restart-policy" class="padding-top" ng-include="'app/components/service/includes/restart.html'"></div>
|
<div id="service-restart-policy" class="padding-top" ng-include="'app/components/service/includes/restart.html'"></div>
|
||||||
<div id="service-update-config" class="padding-top" ng-include="'app/components/service/includes/updateconfig.html'"></div>
|
<div id="service-update-config" class="padding-top" ng-include="'app/components/service/includes/updateconfig.html'"></div>
|
||||||
<div id="service-labels" class="padding-top" ng-include="'app/components/service/includes/servicelabels.html'"></div>
|
<div id="service-labels" class="padding-top" ng-include="'app/components/service/includes/servicelabels.html'"></div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
angular.module('service', [])
|
angular.module('service', [])
|
||||||
.controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'Secret', 'SecretHelper', 'Service', 'ServiceHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline',
|
.controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'Secret', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline',
|
||||||
function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, ServiceService, Secret, SecretHelper, Service, ServiceHelper, TaskService, NodeService, Notifications, Pagination, ModalService, ControllerDataPipeline) {
|
function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll, ServiceService, Secret, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, Notifications, Pagination, ModalService, ControllerDataPipeline) {
|
||||||
|
|
||||||
$scope.state = {};
|
$scope.state = {};
|
||||||
$scope.state.pagination_count = Pagination.getPaginationCount('service_tasks');
|
$scope.state.pagination_count = Pagination.getPaginationCount('service_tasks');
|
||||||
|
@ -124,10 +124,24 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll,
|
||||||
updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints);
|
updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$scope.updatePlacementConstraint = function updatePlacementConstraint(service, constraint) {
|
$scope.updatePlacementConstraint = function(service, constraint) {
|
||||||
updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints);
|
updateServiceArray(service, 'ServiceConstraints', service.ServiceConstraints);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.addPlacementPreference = function(service) {
|
||||||
|
service.ServicePreferences.push({ strategy: 'spread', value: '' });
|
||||||
|
updateServiceArray(service, 'ServicePreferences', service.ServicePreferences);
|
||||||
|
};
|
||||||
|
$scope.removePlacementPreference = function(service, index) {
|
||||||
|
var removedElement = service.ServicePreferences.splice(index, 1);
|
||||||
|
if (removedElement !== null) {
|
||||||
|
updateServiceArray(service, 'ServicePreferences', service.ServicePreferences);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$scope.updatePlacementPreference = function(service, constraint) {
|
||||||
|
updateServiceArray(service, 'ServicePreferences', service.ServicePreferences);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.addPublishedPort = function addPublishedPort(service) {
|
$scope.addPublishedPort = function addPublishedPort(service) {
|
||||||
if (!service.Ports) {
|
if (!service.Ports) {
|
||||||
service.Ports = [];
|
service.Ports = [];
|
||||||
|
@ -174,9 +188,9 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll,
|
||||||
$('#loadingViewSpinner').show();
|
$('#loadingViewSpinner').show();
|
||||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||||
config.Name = service.Name;
|
config.Name = service.Name;
|
||||||
config.Labels = translateServiceLabelsToLabels(service.ServiceLabels);
|
config.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceLabels);
|
||||||
config.TaskTemplate.ContainerSpec.Env = translateEnvironmentVariablesToEnv(service.EnvironmentVariables);
|
config.TaskTemplate.ContainerSpec.Env = ServiceHelper.translateEnvironmentVariablesToEnv(service.EnvironmentVariables);
|
||||||
config.TaskTemplate.ContainerSpec.Labels = translateServiceLabelsToLabels(service.ServiceContainerLabels);
|
config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceContainerLabels);
|
||||||
config.TaskTemplate.ContainerSpec.Image = service.Image;
|
config.TaskTemplate.ContainerSpec.Image = service.Image;
|
||||||
config.TaskTemplate.ContainerSpec.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : [];
|
config.TaskTemplate.ContainerSpec.Secrets = service.ServiceSecrets ? service.ServiceSecrets.map(SecretHelper.secretConfig) : [];
|
||||||
|
|
||||||
|
@ -188,6 +202,7 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll,
|
||||||
config.TaskTemplate.Placement = {};
|
config.TaskTemplate.Placement = {};
|
||||||
}
|
}
|
||||||
config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(service.ServiceConstraints);
|
config.TaskTemplate.Placement.Constraints = ServiceHelper.translateKeyValueToPlacementConstraints(service.ServiceConstraints);
|
||||||
|
config.TaskTemplate.Placement.Preferences = ServiceHelper.translateKeyValueToPlacementPreferences(service.ServicePreferences);
|
||||||
|
|
||||||
config.TaskTemplate.Resources = {
|
config.TaskTemplate.Resources = {
|
||||||
Limits: {
|
Limits: {
|
||||||
|
@ -263,11 +278,12 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll,
|
||||||
|
|
||||||
function translateServiceArrays(service) {
|
function translateServiceArrays(service) {
|
||||||
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
|
service.ServiceSecrets = service.Secrets ? service.Secrets.map(SecretHelper.flattenSecret) : [];
|
||||||
service.EnvironmentVariables = translateEnvironmentVariables(service.Env);
|
service.EnvironmentVariables = ServiceHelper.translateEnvironmentVariables(service.Env);
|
||||||
service.ServiceLabels = translateLabelsToServiceLabels(service.Labels);
|
service.ServiceLabels = LabelHelper.fromLabelHashToKeyValue(service.Labels);
|
||||||
service.ServiceContainerLabels = translateLabelsToServiceLabels(service.ContainerLabels);
|
service.ServiceContainerLabels = LabelHelper.fromLabelHashToKeyValue(service.ContainerLabels);
|
||||||
service.ServiceMounts = angular.copy(service.Mounts);
|
service.ServiceMounts = angular.copy(service.Mounts);
|
||||||
service.ServiceConstraints = translateConstraintsToKeyValue(service.Constraints);
|
service.ServiceConstraints = ServiceHelper.translateConstraintsToKeyValue(service.Constraints);
|
||||||
|
service.ServicePreferences = ServiceHelper.translatePreferencesToKeyValue(service.Preferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
|
@ -310,7 +326,6 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll,
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve service details');
|
Notifications.error('Failure', err, 'Unable to retrieve service details');
|
||||||
})
|
})
|
||||||
.finally(function final() {
|
.finally(function final() {
|
||||||
|
|
||||||
$('#loadingViewSpinner').hide();
|
$('#loadingViewSpinner').hide();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -341,80 +356,5 @@ function ($q, $scope, $stateParams, $state, $location, $timeout, $anchorScroll,
|
||||||
service.hasChanges = true;
|
service.hasChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function translateEnvironmentVariables(env) {
|
|
||||||
if (env) {
|
|
||||||
var variables = [];
|
|
||||||
env.forEach(function(variable) {
|
|
||||||
var idx = variable.indexOf('=');
|
|
||||||
var keyValue = [variable.slice(0,idx), variable.slice(idx+1)];
|
|
||||||
var originalValue = (keyValue.length > 1) ? keyValue[1] : '';
|
|
||||||
variables.push({ key: keyValue[0], value: originalValue, originalKey: keyValue[0], originalValue: originalValue, added: true});
|
|
||||||
});
|
|
||||||
return variables;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
function translateEnvironmentVariablesToEnv(env) {
|
|
||||||
if (env) {
|
|
||||||
var variables = [];
|
|
||||||
env.forEach(function(variable) {
|
|
||||||
if (variable.key && variable.key !== '') {
|
|
||||||
variables.push(variable.key + '=' + variable.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return variables;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateLabelsToServiceLabels(Labels) {
|
|
||||||
var labels = [];
|
|
||||||
if (Labels) {
|
|
||||||
Object.keys(Labels).forEach(function(key) {
|
|
||||||
labels.push({ key: key, value: Labels[key], originalKey: key, originalValue: Labels[key], added: true});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
function translateServiceLabelsToLabels(labels) {
|
|
||||||
var Labels = {};
|
|
||||||
if (labels) {
|
|
||||||
labels.forEach(function(label) {
|
|
||||||
Labels[label.key] = label.value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateConstraintsToKeyValue(constraints) {
|
|
||||||
function getOperator(constraint) {
|
|
||||||
var indexEquals = constraint.indexOf('==');
|
|
||||||
if (indexEquals >= 0) {
|
|
||||||
return [indexEquals, '=='];
|
|
||||||
}
|
|
||||||
return [constraint.indexOf('!='), '!='];
|
|
||||||
}
|
|
||||||
if (constraints) {
|
|
||||||
var keyValueConstraints = [];
|
|
||||||
constraints.forEach(function(constraint) {
|
|
||||||
var operatorIndices = getOperator(constraint);
|
|
||||||
|
|
||||||
var key = constraint.slice(0, operatorIndices[0]);
|
|
||||||
var operator = operatorIndices[1];
|
|
||||||
var value = constraint.slice(operatorIndices[0] + 2);
|
|
||||||
|
|
||||||
keyValueConstraints.push({
|
|
||||||
key: key,
|
|
||||||
value: value,
|
|
||||||
operator: operator,
|
|
||||||
originalKey: key,
|
|
||||||
originalValue: value
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return keyValueConstraints;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
angular.module('portainer.helpers')
|
angular.module('portainer.helpers').factory('ServiceHelper', [function ServiceHelperFactory() {
|
||||||
.factory('ServiceHelper', [function ServiceHelperFactory() {
|
|
||||||
'use strict';
|
'use strict';
|
||||||
return {
|
return {
|
||||||
serviceToConfig: function(service) {
|
serviceToConfig: function(service) {
|
||||||
|
@ -13,17 +12,113 @@ angular.module('portainer.helpers')
|
||||||
EndpointSpec: service.Spec.EndpointSpec
|
EndpointSpec: service.Spec.EndpointSpec
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
translateKeyValueToPlacementPreferences: function(keyValuePreferences) {
|
||||||
|
if (keyValuePreferences) {
|
||||||
|
var preferences = [];
|
||||||
|
keyValuePreferences.forEach(function(preference) {
|
||||||
|
if (preference.strategy && preference.strategy !== '' && preference.value && preference.value !== '') {
|
||||||
|
switch (preference.strategy.toLowerCase()) {
|
||||||
|
case 'spread':
|
||||||
|
preferences.push({
|
||||||
|
'Spread': {
|
||||||
|
'SpreadDescriptor': preference.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
translateKeyValueToPlacementConstraints: function(keyValueConstraints) {
|
translateKeyValueToPlacementConstraints: function(keyValueConstraints) {
|
||||||
if (keyValueConstraints) {
|
if (keyValueConstraints) {
|
||||||
var constraints = [];
|
var constraints = [];
|
||||||
keyValueConstraints.forEach(function(keyValueConstraint) {
|
keyValueConstraints.forEach(function(constraint) {
|
||||||
if (keyValueConstraint.key && keyValueConstraint.key !== '' && keyValueConstraint.value && keyValueConstraint.value !== '') {
|
if (constraint.key && constraint.key !== '' && constraint.value && constraint.value !== '') {
|
||||||
constraints.push(keyValueConstraint.key + keyValueConstraint.operator + keyValueConstraint.value);
|
constraints.push(constraint.key + constraint.operator + constraint.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return constraints;
|
return constraints;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
},
|
||||||
|
translateEnvironmentVariables: function(env) {
|
||||||
|
if (env) {
|
||||||
|
var variables = [];
|
||||||
|
env.forEach(function(variable) {
|
||||||
|
var idx = variable.indexOf('=');
|
||||||
|
var keyValue = [variable.slice(0, idx), variable.slice(idx + 1)];
|
||||||
|
var originalValue = (keyValue.length > 1) ? keyValue[1] : '';
|
||||||
|
variables.push({
|
||||||
|
key: keyValue[0],
|
||||||
|
value: originalValue,
|
||||||
|
originalKey: keyValue[0],
|
||||||
|
originalValue: originalValue,
|
||||||
|
added: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
translateEnvironmentVariablesToEnv: function(env) {
|
||||||
|
if (env) {
|
||||||
|
var variables = [];
|
||||||
|
env.forEach(function(variable) {
|
||||||
|
if (variable.key && variable.key !== '') {
|
||||||
|
variables.push(variable.key + '=' + variable.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
translatePreferencesToKeyValue: function(preferences) {
|
||||||
|
if (preferences) {
|
||||||
|
var keyValuePreferences = [];
|
||||||
|
preferences.forEach(function(preference) {
|
||||||
|
if (preference.Spread) {
|
||||||
|
keyValuePreferences.push({
|
||||||
|
strategy: 'Spread',
|
||||||
|
value: preference.Spread.SpreadDescriptor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return keyValuePreferences;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
translateConstraintsToKeyValue: function(constraints) {
|
||||||
|
function getOperator(constraint) {
|
||||||
|
var indexEquals = constraint.indexOf('==');
|
||||||
|
if (indexEquals >= 0) {
|
||||||
|
return [indexEquals, '=='];
|
||||||
|
}
|
||||||
|
return [constraint.indexOf('!='), '!='];
|
||||||
|
}
|
||||||
|
if (constraints) {
|
||||||
|
var keyValueConstraints = [];
|
||||||
|
constraints.forEach(function(constraint) {
|
||||||
|
var operatorIndices = getOperator(constraint);
|
||||||
|
|
||||||
|
var key = constraint.slice(0, operatorIndices[0]);
|
||||||
|
var operator = operatorIndices[1];
|
||||||
|
var value = constraint.slice(operatorIndices[0] + 2);
|
||||||
|
|
||||||
|
keyValueConstraints.push({
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
operator: operator,
|
||||||
|
originalKey: key,
|
||||||
|
originalValue: value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return keyValueConstraints;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -41,27 +41,37 @@ function ServiceViewModel(data, runningTasks, nodes) {
|
||||||
this.RestartWindow = 0;
|
this.RestartWindow = 0;
|
||||||
}
|
}
|
||||||
this.Constraints = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Constraints || [] : [];
|
this.Constraints = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Constraints || [] : [];
|
||||||
|
this.Preferences = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Preferences || [] : [];
|
||||||
|
this.Platforms = data.Spec.TaskTemplate.Placement ? data.Spec.TaskTemplate.Placement.Platforms || [] : [];
|
||||||
this.Labels = data.Spec.Labels;
|
this.Labels = data.Spec.Labels;
|
||||||
|
|
||||||
var containerSpec = data.Spec.TaskTemplate.ContainerSpec;
|
var containerSpec = data.Spec.TaskTemplate.ContainerSpec;
|
||||||
if (containerSpec) {
|
if (containerSpec) {
|
||||||
this.ContainerLabels = containerSpec.Labels;
|
this.ContainerLabels = containerSpec.Labels;
|
||||||
this.Env = containerSpec.Env;
|
|
||||||
this.Mounts = containerSpec.Mounts || [];
|
|
||||||
this.User = containerSpec.User;
|
|
||||||
this.Dir = containerSpec.Dir;
|
|
||||||
this.Command = containerSpec.Command;
|
this.Command = containerSpec.Command;
|
||||||
this.Arguments = containerSpec.Args;
|
this.Arguments = containerSpec.Args;
|
||||||
|
this.Hostname = containerSpec.Hostname;
|
||||||
|
this.Env = containerSpec.Env;
|
||||||
|
this.Dir = containerSpec.Dir;
|
||||||
|
this.User = containerSpec.User;
|
||||||
|
this.Groups = containerSpec.Groups;
|
||||||
|
this.TTY = containerSpec.TTY;
|
||||||
|
this.OpenStdin = containerSpec.OpenStdin;
|
||||||
|
this.ReadOnly = containerSpec.ReadOnly;
|
||||||
|
this.Mounts = containerSpec.Mounts || [];
|
||||||
|
this.StopSignal = containerSpec.StopSignal;
|
||||||
|
this.StopGracePeriod = containerSpec.StopGracePeriod;
|
||||||
|
this.HealthCheck = containerSpec.HealthCheck || {};
|
||||||
|
this.Hosts = containerSpec.Hosts;
|
||||||
|
this.DNSConfig = containerSpec.DNSConfig;
|
||||||
this.Secrets = containerSpec.Secrets;
|
this.Secrets = containerSpec.Secrets;
|
||||||
}
|
}
|
||||||
if (data.Endpoint) {
|
if (data.Endpoint) {
|
||||||
this.Ports = data.Endpoint.Ports;
|
this.Ports = data.Endpoint.Ports;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Mounts = [];
|
this.LogDriver = data.Spec.TaskTemplate.LogDriver;
|
||||||
if (data.Spec.TaskTemplate.ContainerSpec.Mounts) {
|
this.Runtime = data.Spec.TaskTemplate.Runtime;
|
||||||
this.Mounts = data.Spec.TaskTemplate.ContainerSpec.Mounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.VirtualIPs = data.Endpoint ? data.Endpoint.VirtualIPs : [];
|
this.VirtualIPs = data.Endpoint ? data.Endpoint.VirtualIPs : [];
|
||||||
|
|
||||||
|
@ -75,6 +85,8 @@ function ServiceViewModel(data, runningTasks, nodes) {
|
||||||
this.UpdateFailureAction = 'pause';
|
this.UpdateFailureAction = 'pause';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.RollbackConfig = data.Spec.RollbackConfig;
|
||||||
|
|
||||||
this.Checked = false;
|
this.Checked = false;
|
||||||
this.Scale = false;
|
this.Scale = false;
|
||||||
this.EditName = false;
|
this.EditName = false;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue