mirror of https://github.com/portainer/portainer
feat(app): push pull service details update
parent
5a104c43b8
commit
332aa4fbf5
|
@ -0,0 +1,37 @@
|
||||||
|
<div id="service-container-image" authorization="DockerServiceUpdate">
|
||||||
|
<rd-widget>
|
||||||
|
<rd-widget-header icon="fa-clone" title-text="Change container image">
|
||||||
|
</rd-widget-header>
|
||||||
|
<rd-widget-body ng-if="!isUpdating">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<por-image-registry
|
||||||
|
model="formValues.RegistryModel"
|
||||||
|
auto-complete="true"
|
||||||
|
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
||||||
|
></por-image-registry>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<span class="small text-muted">Note: if you don't specify the tag in the image name, <span class="label label-default">latest</span> will be used.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget-body ng-if="isUpdating">
|
||||||
|
<p>Image modification is disabled while service is updating.</p>
|
||||||
|
</rd-widget-body>
|
||||||
|
<rd-widget-footer authorization="DockerServiceUpdate">
|
||||||
|
<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, ['Image'])" 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, ['Image'])">Reset changes</a></li>
|
||||||
|
<li><a ng-click="cancelChanges(service)">Reset all changes</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</rd-widget-footer>
|
||||||
|
</rd-widget>
|
||||||
|
</div>
|
|
@ -66,10 +66,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Image</td>
|
<td>Image</td>
|
||||||
<td>
|
<td>{{ service.Image }}</td>
|
||||||
<input type="text" class="form-control" uib-typeahead="image for image in availableImages | filter:$viewValue | limitTo:5"
|
|
||||||
ng-model="service.Image" ng-change="updateServiceAttribute(service, 'Image')" id="image_name" disable-authorization="DockerServiceUpdate">
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-if="applicationState.endpoint.type !== 4">
|
<tr ng-if="applicationState.endpoint.type !== 4">
|
||||||
<td colspan="{{webhookURL ? '1' : '2'}}">
|
<td colspan="{{webhookURL ? '1' : '2'}}">
|
||||||
|
@ -118,12 +115,12 @@
|
||||||
</p>
|
</p>
|
||||||
<div class="btn-toolbar" role="toolbar">
|
<div class="btn-toolbar" role="toolbar">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button type="button" class="btn btn-primary" ng-disabled="!hasChanges(service, ['Mode', 'Replicas', 'Image', 'Name', 'Webhooks'])" ng-click="updateService(service)">Apply changes</button>
|
<button type="button" class="btn btn-primary" ng-disabled="!hasChanges(service, ['Mode', 'Replicas', 'Name', 'Webhooks'])" ng-click="updateService(service)">Apply changes</button>
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a ng-click="cancelChanges(service, ['Mode', 'Replicas', 'Image', 'Name'])">Reset changes</a></li>
|
<li><a ng-click="cancelChanges(service, ['Mode', 'Replicas', 'Name'])">Reset changes</a></li>
|
||||||
<li><a ng-click="cancelChanges(service)">Reset all changes</a></li>
|
<li><a ng-click="cancelChanges(service)">Reset all changes</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -138,6 +135,7 @@
|
||||||
<rd-widget-body classes="no-padding">
|
<rd-widget-body classes="no-padding">
|
||||||
<ul class="nav nav-pills nav-stacked">
|
<ul class="nav nav-pills nav-stacked">
|
||||||
<li><a href ng-click="goToItem('service-env-variables')">Environment variables</a></li>
|
<li><a href ng-click="goToItem('service-env-variables')">Environment variables</a></li>
|
||||||
|
<li><a href ng-click="goToItem('service-container-image')">Container image</a></li>
|
||||||
<li><a href ng-click="goToItem('service-container-labels')">Container labels</a></li>
|
<li><a href ng-click="goToItem('service-container-labels')">Container labels</a></li>
|
||||||
<li><a href ng-click="goToItem('service-mounts')">Mounts</a></li>
|
<li><a href ng-click="goToItem('service-mounts')">Mounts</a></li>
|
||||||
<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>
|
||||||
|
@ -171,6 +169,7 @@
|
||||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
<h3 id="container-specs">Container specification</h3>
|
<h3 id="container-specs">Container specification</h3>
|
||||||
<div id="service-container-spec" class="padding-top" ng-include="'app/docker/views/services/edit/includes/container-specs.html'"></div>
|
<div id="service-container-spec" class="padding-top" ng-include="'app/docker/views/services/edit/includes/container-specs.html'"></div>
|
||||||
|
<div id="service-container-image" class="padding-top" ng-include="'app/docker/views/services/edit/includes/image.html'"></div>
|
||||||
<div id="service-env-variables" class="padding-top" ng-include="'app/docker/views/services/edit/includes/environmentvariables.html'"></div>
|
<div id="service-env-variables" class="padding-top" ng-include="'app/docker/views/services/edit/includes/environmentvariables.html'"></div>
|
||||||
<div id="service-container-labels" class="padding-top" ng-include="'app/docker/views/services/edit/includes/containerlabels.html'"></div>
|
<div id="service-container-labels" class="padding-top" ng-include="'app/docker/views/services/edit/includes/containerlabels.html'"></div>
|
||||||
<div id="service-mounts" class="padding-top" ng-include="'app/docker/views/services/edit/includes/mounts.html'"></div>
|
<div id="service-mounts" class="padding-top" ng-include="'app/docker/views/services/edit/includes/mounts.html'"></div>
|
||||||
|
|
|
@ -4,6 +4,7 @@ require('./includes/container-specs.html')
|
||||||
require('./includes/containerlabels.html')
|
require('./includes/containerlabels.html')
|
||||||
require('./includes/environmentvariables.html')
|
require('./includes/environmentvariables.html')
|
||||||
require('./includes/hosts.html')
|
require('./includes/hosts.html')
|
||||||
|
require('./includes/image.html')
|
||||||
require('./includes/logging.html')
|
require('./includes/logging.html')
|
||||||
require('./includes/mounts.html')
|
require('./includes/mounts.html')
|
||||||
require('./includes/networks.html')
|
require('./includes/networks.html')
|
||||||
|
@ -16,6 +17,8 @@ require('./includes/servicelabels.html')
|
||||||
require('./includes/tasks.html')
|
require('./includes/tasks.html')
|
||||||
require('./includes/updateconfig.html')
|
require('./includes/updateconfig.html')
|
||||||
|
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
angular.module('portainer.docker')
|
||||||
.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'ContainerService', 'TaskHelper', 'Notifications', 'ModalService', 'PluginService', 'Authentication', 'SettingsService', 'VolumeService', 'ImageHelper', 'WebhookService', 'EndpointProvider', 'clipboard','WebhookHelper',
|
.controller('ServiceController', ['$q', '$scope', '$transition$', '$state', '$location', '$timeout', '$anchorScroll', 'ServiceService', 'ConfigService', 'ConfigHelper', 'SecretService', 'ImageService', 'SecretHelper', 'Service', 'ServiceHelper', 'LabelHelper', 'TaskService', 'NodeService', 'ContainerService', 'TaskHelper', 'Notifications', 'ModalService', 'PluginService', 'Authentication', 'SettingsService', 'VolumeService', 'ImageHelper', 'WebhookService', 'EndpointProvider', 'clipboard','WebhookHelper',
|
||||||
function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, ContainerService, TaskHelper, Notifications, ModalService, PluginService, Authentication, SettingsService, VolumeService, ImageHelper, WebhookService, EndpointProvider, clipboard, WebhookHelper) {
|
function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll, ServiceService, ConfigService, ConfigHelper, SecretService, ImageService, SecretHelper, Service, ServiceHelper, LabelHelper, TaskService, NodeService, ContainerService, TaskHelper, Notifications, ModalService, PluginService, Authentication, SettingsService, VolumeService, ImageHelper, WebhookService, EndpointProvider, clipboard, WebhookHelper) {
|
||||||
|
@ -26,6 +29,10 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
rollbackInProgress: false,
|
rollbackInProgress: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.formValues = {
|
||||||
|
RegistryModel: new PorImageRegistryModel()
|
||||||
|
};
|
||||||
|
|
||||||
$scope.tasks = [];
|
$scope.tasks = [];
|
||||||
$scope.availableImages = [];
|
$scope.availableImages = [];
|
||||||
|
|
||||||
|
@ -34,20 +41,6 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
var originalService = {};
|
var originalService = {};
|
||||||
var previousServiceValues = [];
|
var previousServiceValues = [];
|
||||||
|
|
||||||
$scope.renameService = function renameService(service) {
|
|
||||||
updateServiceAttribute(service, 'Name', service.newServiceName || service.name);
|
|
||||||
service.EditName = false;
|
|
||||||
};
|
|
||||||
$scope.changeServiceImage = function changeServiceImage(service) {
|
|
||||||
updateServiceAttribute(service, 'Image', service.newServiceImage || service.image);
|
|
||||||
service.EditImage = false;
|
|
||||||
};
|
|
||||||
$scope.scaleService = function scaleService(service) {
|
|
||||||
var replicas = service.newServiceReplicas === null || isNaN(service.newServiceReplicas) ? service.Replicas : service.newServiceReplicas;
|
|
||||||
updateServiceAttribute(service, 'Replicas', replicas);
|
|
||||||
service.EditReplicas = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.goToItem = function(hash) {
|
$scope.goToItem = function(hash) {
|
||||||
if ($location.hash() !== hash) {
|
if ($location.hash() !== hash) {
|
||||||
$location.hash(hash);
|
$location.hash(hash);
|
||||||
|
@ -259,12 +252,17 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
$scope.cancelChanges = function cancelChanges(service, keys) {
|
$scope.cancelChanges = function cancelChanges(service, keys) {
|
||||||
if (keys) { // clean out the keys only from the list of modified keys
|
if (keys) { // clean out the keys only from the list of modified keys
|
||||||
keys.forEach(function(key) {
|
keys.forEach(function(key) {
|
||||||
var index = previousServiceValues.indexOf(key);
|
if (key === 'Image') {
|
||||||
if (index >= 0) {
|
$scope.formValues.RegistryModel.Image = '';
|
||||||
previousServiceValues.splice(index, 1);
|
} else {
|
||||||
|
var index = previousServiceValues.indexOf(key);
|
||||||
|
if (index >= 0) {
|
||||||
|
previousServiceValues.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else { // clean out all changes
|
} else { // clean out all changes
|
||||||
|
$scope.formValues.RegistryModel.Image = '';
|
||||||
keys = Object.keys(service);
|
keys = Object.keys(service);
|
||||||
previousServiceValues = [];
|
previousServiceValues = [];
|
||||||
}
|
}
|
||||||
|
@ -277,7 +275,11 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
$scope.hasChanges = function(service, elements) {
|
$scope.hasChanges = function(service, elements) {
|
||||||
var hasChanges = false;
|
var hasChanges = false;
|
||||||
elements.forEach(function(key) {
|
elements.forEach(function(key) {
|
||||||
hasChanges = hasChanges || (previousServiceValues.indexOf(key) >= 0);
|
if (key === 'Image') {
|
||||||
|
hasChanges = hasChanges || $scope.formValues.RegistryModel.Image ? true : false;
|
||||||
|
} else {
|
||||||
|
hasChanges = hasChanges || (previousServiceValues.indexOf(key) >= 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return hasChanges;
|
return hasChanges;
|
||||||
};
|
};
|
||||||
|
@ -288,7 +290,14 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
config.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceLabels);
|
config.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceLabels);
|
||||||
config.TaskTemplate.ContainerSpec.Env = ServiceHelper.translateEnvironmentVariablesToEnv(service.EnvironmentVariables);
|
config.TaskTemplate.ContainerSpec.Env = ServiceHelper.translateEnvironmentVariablesToEnv(service.EnvironmentVariables);
|
||||||
config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceContainerLabels);
|
config.TaskTemplate.ContainerSpec.Labels = LabelHelper.fromKeyValueToLabelHash(service.ServiceContainerLabels);
|
||||||
config.TaskTemplate.ContainerSpec.Image = service.Image;
|
|
||||||
|
if ($scope.hasChanges(service, ["Image"])) {
|
||||||
|
const image = ImageHelper.createImageConfigForContainer($scope.formValues.RegistryModel);
|
||||||
|
config.TaskTemplate.ContainerSpec.Image = image.fromImage;
|
||||||
|
} else {
|
||||||
|
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) : [];
|
||||||
config.TaskTemplate.ContainerSpec.Configs = service.ServiceConfigs ? service.ServiceConfigs.map(ConfigHelper.configConfig) : [];
|
config.TaskTemplate.ContainerSpec.Configs = service.ServiceConfigs ? service.ServiceConfigs.map(ConfigHelper.configConfig) : [];
|
||||||
config.TaskTemplate.ContainerSpec.Hosts = service.Hosts ? ServiceHelper.translateHostnameIPToHostsEntries(service.Hosts) : [];
|
config.TaskTemplate.ContainerSpec.Hosts = service.Hosts ? ServiceHelper.translateHostnameIPToHostsEntries(service.Hosts) : [];
|
||||||
|
@ -468,7 +477,7 @@ function ($q, $scope, $transition$, $state, $location, $timeout, $anchorScroll,
|
||||||
function forceUpdateService(service, pullImage) {
|
function forceUpdateService(service, pullImage) {
|
||||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||||
if (pullImage) {
|
if (pullImage) {
|
||||||
config.TaskTemplate.ContainerSpec.Image = config.TaskTemplate.ContainerSpec.Image = ImageHelper.removeDigestFromRepository(config.TaskTemplate.ContainerSpec.Image);
|
config.TaskTemplate.ContainerSpec.Image = ImageHelper.removeDigestFromRepository(config.TaskTemplate.ContainerSpec.Image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// As explained in https://github.com/docker/swarmkit/issues/2364 ForceUpdate can accept a random
|
// As explained in https://github.com/docker/swarmkit/issues/2364 ForceUpdate can accept a random
|
||||||
|
|
Loading…
Reference in New Issue