mirror of https://github.com/portainer/portainer
fix(app): registry push-pull features overhaul (#3393)
* feat(registry): registry or direct url selector
* feat(app): push pull container creation
* feat(app): push pull container duplicate
* feat(app): push pull container details recreate
* feat(app): push pull container details commit
* feat(app): push pull images
* feat(app): push pull image tag
* feat(app): push pull image push
* feat(app): push pull image pull
* feat(app): push pull service creation
* feat(app): push pull templates create container
* feat(app): push pull templates create stacks
* feat(app): push pull template edit
* feat(app): push pull service details update
* fix(app): refactor registry selector + registry auto select
* feat(app): remove autocomplete on registry selector
* style(image-registry): reword simple/advanced mode
* Revert "feat(app): remove autocomplete on registry selector"
This reverts commit 97ec2ddd62
.
* refactor(registry-selector): reverse registry and image fields
* feat(app): autocomplete on registry selector
* feat(registry-selector): change gitlab registry autocomplete
* feat(registry-selector): autocomplete for dockerhub
* feat(registry-selector): gitlab url based on locked value instead of name
* fix(registry-selector): gitlab registries URL are not modified anymore
* fix(registry-selector): change gitlab image autofill on duplicate
* fix(registry-selector): gitlab registries now only suggest their own images and not all from gitlab
* fix(registry-selector): psuh pull issues with gitlab registries
* fix(registry-selector): dockerhub registry selection on duplicate for dockerhub images
* fix(templates): registry retrieval for template
* feat(images): add autocomplete on image pull panel
* fix(registry-selector): add latest tag when no tag is specified
* fix(registry-selector): latest tag now applied for non gitlab registries
pull/3403/head
parent
61c38534a7
commit
e19bc8abc7
|
@ -196,6 +196,7 @@ type (
|
||||||
GitlabRegistryData struct {
|
GitlabRegistryData struct {
|
||||||
ProjectID int `json:"ProjectId"`
|
ProjectID int `json:"ProjectId"`
|
||||||
InstanceURL string `json:"InstanceURL"`
|
InstanceURL string `json:"InstanceURL"`
|
||||||
|
ProjectPath string `json:"ProjectPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registry represents a Docker registry with all the info required
|
// Registry represents a Docker registry with all the info required
|
||||||
|
|
|
@ -2,8 +2,8 @@ angular.module('portainer.docker').component('porImageRegistry', {
|
||||||
templateUrl: './porImageRegistry.html',
|
templateUrl: './porImageRegistry.html',
|
||||||
controller: 'porImageRegistryController',
|
controller: 'porImageRegistryController',
|
||||||
bindings: {
|
bindings: {
|
||||||
'image': '=',
|
'model': '=', // must be of type PorImageRegistryModel
|
||||||
'registry': '=',
|
'pullWarning': '<',
|
||||||
'autoComplete': '<',
|
'autoComplete': '<',
|
||||||
'labelClass': '@',
|
'labelClass': '@',
|
||||||
'inputClass': '@'
|
'inputClass': '@'
|
||||||
|
|
|
@ -1,21 +1,54 @@
|
||||||
<div class="form-group">
|
<!-- use registry -->
|
||||||
<label for="image_name" ng-class="$ctrl.labelClass" class="control-label text-left">Image</label>
|
<div ng-if="$ctrl.model.UseRegistry">
|
||||||
<div ng-class="$ctrl.inputClass">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" uib-typeahead="image for image in $ctrl.availableImages | filter:$viewValue | limitTo:5"
|
<label for="image_registry" class="control-label text-left" ng-class="$ctrl.labelClass">
|
||||||
ng-model="$ctrl.image" name="image_name" placeholder="e.g. myImage:myTag" required>
|
Registry
|
||||||
</div>
|
</label>
|
||||||
<label for="image_registry" class="margin-sm-top control-label text-right" ng-class="$ctrl.labelClass">
|
<div ng-class="$ctrl.inputClass">
|
||||||
Registry
|
<select ng-options="registry as registry.Name for registry in $ctrl.availableRegistries track by registry.Name" ng-model="$ctrl.model.Registry" id="image_registry"
|
||||||
</label>
|
selected-item-id="ctrl.selectedItemId" class="form-control"></select>
|
||||||
<div ng-class="$ctrl.inputClass" class="margin-sm-top">
|
</div>
|
||||||
<select ng-options="registry as registry.Name for registry in $ctrl.availableRegistries" ng-model="$ctrl.registry" id="image_registry"
|
<label for="image_name" ng-class="$ctrl.labelClass" class="margin-sm-top control-label text-left">Image</label>
|
||||||
class="form-control"></select>
|
<div ng-class="$ctrl.inputClass" class="margin-sm-top">
|
||||||
</div>
|
<div class="input-group">
|
||||||
</div>
|
<span class="input-group-addon" id="registry-name">{{$ctrl.displayedRegistryURL()}}</span>
|
||||||
<div class="form-group" ng-show="$ctrl.form.image_name.$invalid">
|
<input type="text" class="form-control" aria-describedby="registry-name" uib-typeahead="image for image in $ctrl.availableImages | filter:$viewValue | limitTo:5"
|
||||||
<div class="col-sm-12 small text-warning">
|
ng-model="$ctrl.model.Image" name="image_name" placeholder="e.g. myImage:myTag" required>
|
||||||
<div ng-messages="$ctrl.form.image_name.$error">
|
</div>
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Image name is required.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- ! use registry -->
|
||||||
|
<!-- don't use registry -->
|
||||||
|
<div ng-if="!$ctrl.model.UseRegistry">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image_name" ng-class="$ctrl.labelClass" class="control-label text-left">Image
|
||||||
|
<portainer-tooltip position="bottom" message="Image and repository should be publicly available."></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div ng-class="$ctrl.inputClass">
|
||||||
|
<input type="text" class="form-control" ng-model="$ctrl.model.Image" name="image_name" placeholder="e.g. registry:port/myImage:myTag" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- ! don't use registry -->
|
||||||
|
<!-- info message -->
|
||||||
|
<div class="form-group" ng-show="$ctrl.form.image_name.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="$ctrl.form.image_name.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Image name is required. <span ng-if="$ctrl.canPull">Tag must be specified otherwise Portainer will pull all tags associated to the image.</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- ! info message -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<p>
|
||||||
|
<a class="small interactive" ng-if="!$ctrl.model.UseRegistry" ng-click="$ctrl.model.UseRegistry = true;">
|
||||||
|
<i class="fa fa-database space-right" aria-hidden="true"></i> Simple mode
|
||||||
|
</a>
|
||||||
|
<a class="small interactive" ng-if="$ctrl.model.UseRegistry" ng-click="$ctrl.model.UseRegistry = false;">
|
||||||
|
<i class="fa fa-globe space-right" aria-hidden="true"></i> Advanced mode
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,31 +1,89 @@
|
||||||
|
import angular from 'angular';
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import { DockerHubViewModel } from 'Portainer/models/dockerhub';
|
||||||
|
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
class porImageRegistryController {
|
||||||
.controller('porImageRegistryController', ['$q', 'RegistryService', 'DockerHubService', 'ImageService', 'Notifications',
|
/* @ngInject */
|
||||||
function ($q, RegistryService, DockerHubService, ImageService, Notifications) {
|
constructor($async, $scope, ImageHelper, RegistryService, DockerHubService, ImageService, Notifications) {
|
||||||
var ctrl = this;
|
this.$async = $async;
|
||||||
|
this.$scope = $scope;
|
||||||
|
this.ImageHelper = ImageHelper;
|
||||||
|
this.RegistryService = RegistryService;
|
||||||
|
this.DockerHubService = DockerHubService;
|
||||||
|
this.ImageService = ImageService;
|
||||||
|
this.Notifications = Notifications;
|
||||||
|
|
||||||
function initComponent() {
|
this.onInit = this.onInit.bind(this);
|
||||||
$q.all({
|
this.onRegistryChange = this.onRegistryChange.bind(this);
|
||||||
registries: RegistryService.registries(),
|
|
||||||
dockerhub: DockerHubService.dockerhub(),
|
this.$scope.$watch(() => this.model.Registry, this.onRegistryChange);
|
||||||
availableImages: ctrl.autoComplete ? ImageService.images() : []
|
|
||||||
})
|
|
||||||
.then(function success(data) {
|
|
||||||
var dockerhub = data.dockerhub;
|
|
||||||
var registries = data.registries;
|
|
||||||
ctrl.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
|
|
||||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
|
||||||
if (!ctrl.registry.Id) {
|
|
||||||
ctrl.registry = dockerhub;
|
|
||||||
} else {
|
|
||||||
ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initComponent();
|
isKnownRegistry(registry) {
|
||||||
}]);
|
return !(registry instanceof DockerHubViewModel) && registry.URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegistryURL(registry) {
|
||||||
|
let url = registry.URL;
|
||||||
|
if (registry.Type === RegistryTypes.GITLAB) {
|
||||||
|
url = registry.URL + '/' + registry.Gitlab.ProjectPath;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareAutocomplete() {
|
||||||
|
let images = [];
|
||||||
|
const registry = this.model.Registry;
|
||||||
|
if (this.isKnownRegistry(registry)) {
|
||||||
|
const url = this.getRegistryURL(registry);
|
||||||
|
const registryImages = _.filter(this.images, (image) => _.includes(image, url));
|
||||||
|
images = _.map(registryImages, (image) => _.replace(image, new RegExp(url + '\/?'), ''));
|
||||||
|
} else {
|
||||||
|
const registries = _.filter(this.availableRegistries, (reg) => this.isKnownRegistry(reg));
|
||||||
|
const registryImages = _.flatMap(registries, (registry) => _.filter(this.images, (image) => _.includes(image, registry.URL)));
|
||||||
|
const imagesWithoutKnown = _.difference(this.images, registryImages);
|
||||||
|
images = _.filter(imagesWithoutKnown, (image) => !this.ImageHelper.imageContainsURL(image));
|
||||||
|
}
|
||||||
|
this.availableImages = images;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRegistryChange() {
|
||||||
|
this.prepareAutocomplete();
|
||||||
|
if (this.model.Registry.Type === RegistryTypes.GITLAB && this.model.Image) {
|
||||||
|
this.model.Image = _.replace(this.model.Image, this.model.Registry.Gitlab.ProjectPath, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displayedRegistryURL() {
|
||||||
|
return this.getRegistryURL(this.model.Registry) || 'docker.io';
|
||||||
|
}
|
||||||
|
|
||||||
|
async onInit() {
|
||||||
|
try {
|
||||||
|
const [registries, dockerhub, images] = await Promise.all([
|
||||||
|
this.RegistryService.registries(),
|
||||||
|
this.DockerHubService.dockerhub(),
|
||||||
|
this.autoComplete ? this.ImageService.images() : []
|
||||||
|
]);
|
||||||
|
this.images = this.ImageService.getUniqueTagListFromImages(images);
|
||||||
|
this.availableRegistries = _.concat(dockerhub, registries);
|
||||||
|
|
||||||
|
const id = this.model.Registry.Id;
|
||||||
|
if (!id) {
|
||||||
|
this.model.Registry = dockerhub;
|
||||||
|
} else {
|
||||||
|
this.model.Registry = _.find(this.availableRegistries, { 'Id': id });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
return this.$async(this.onInit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default porImageRegistryController;
|
||||||
|
angular.module('portainer.docker').controller('porImageRegistryController', porImageRegistryController);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
angular.module('portainer.docker')
|
||||||
.factory('ImageHelper', [function ImageHelperFactory() {
|
.factory('ImageHelper', [function ImageHelperFactory() {
|
||||||
|
@ -7,74 +8,65 @@ angular.module('portainer.docker')
|
||||||
var helper = {};
|
var helper = {};
|
||||||
|
|
||||||
helper.isValidTag = isValidTag;
|
helper.isValidTag = isValidTag;
|
||||||
|
helper.createImageConfigForContainer = createImageConfigForContainer;
|
||||||
|
helper.getImagesNamesForDownload = getImagesNamesForDownload;
|
||||||
|
helper.removeDigestFromRepository = removeDigestFromRepository;
|
||||||
|
helper.imageContainsURL = imageContainsURL;
|
||||||
|
|
||||||
function isValidTag(tag) {
|
function isValidTag(tag) {
|
||||||
return tag.match(/^(?![\.\-])([a-zA-Z0-9\_\.\-])+$/g);
|
return tag.match(/^(?![\.\-])([a-zA-Z0-9\_\.\-])+$/g);
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.extractImageAndRegistryFromRepository = function(repository) {
|
function getImagesNamesForDownload(images) {
|
||||||
var slashCount = _.countBy(repository)['/'];
|
|
||||||
var registry = null;
|
|
||||||
var image = repository;
|
|
||||||
if (slashCount >= 1) {
|
|
||||||
// assume something/something[/...]
|
|
||||||
registry = repository.substr(0, repository.indexOf('/'));
|
|
||||||
// assume valid DNS name or IP (contains at least one '.')
|
|
||||||
if (_.countBy(registry)['.'] > 0) {
|
|
||||||
image = repository.substr(repository.indexOf('/') + 1);
|
|
||||||
} else {
|
|
||||||
registry = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
registry: registry,
|
|
||||||
image: image
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
helper.getImagesNamesForDownload = function(images) {
|
|
||||||
var names = images.map(function(image) {
|
var names = images.map(function(image) {
|
||||||
return image.RepoTags[0] !== '<none>:<none>' ? image.RepoTags[0] : image.Id;
|
return image.RepoTags[0] !== '<none>:<none>' ? image.RepoTags[0] : image.Id;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
names: names
|
names: names
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
function extractNameAndTag(imageName, registry) {
|
|
||||||
var imageNameAndTag = imageName.split(':');
|
|
||||||
var image = imageNameAndTag[0];
|
|
||||||
var tag = imageNameAndTag[1] ? imageNameAndTag[1] : 'latest';
|
|
||||||
if (registry) {
|
|
||||||
image = registry + '/' + imageNameAndTag[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
image: image,
|
|
||||||
tag: tag
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.createImageConfigForCommit = function(imageName, registry) {
|
/**
|
||||||
var imageAndTag = extractNameAndTag(imageName, registry);
|
*
|
||||||
return {
|
* @param {PorImageRegistryModel} registry
|
||||||
repo: imageAndTag.image,
|
*/
|
||||||
tag: imageAndTag.tag
|
function createImageConfigForContainer(registry) {
|
||||||
|
const data = {
|
||||||
|
fromImage: ''
|
||||||
};
|
};
|
||||||
};
|
let fullImageName = '';
|
||||||
|
|
||||||
helper.createImageConfigForContainer = function (imageName, registry) {
|
if (registry.UseRegistry) {
|
||||||
var imageAndTag = extractNameAndTag(imageName, registry);
|
if (registry.Registry.Type === RegistryTypes.GITLAB) {
|
||||||
return {
|
const slash = _.startsWith(registry.Image, ':') ? '' : '/';
|
||||||
fromImage: imageAndTag.image,
|
fullImageName = registry.Registry.URL + '/' + registry.Registry.Gitlab.ProjectPath + slash + registry.Image;
|
||||||
tag: imageAndTag.tag
|
} else {
|
||||||
};
|
const url = registry.Registry.URL ? registry.Registry.URL + '/' : '';
|
||||||
};
|
fullImageName = url + registry.Image;
|
||||||
|
}
|
||||||
|
if (!_.includes(registry.Image, ':')) {
|
||||||
|
fullImageName += ':latest';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fullImageName = registry.Image;
|
||||||
|
}
|
||||||
|
|
||||||
helper.removeDigestFromRepository = function(repository) {
|
data.fromImage = fullImageName;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageContainsURL(image) {
|
||||||
|
const split = _.split(image, '/');
|
||||||
|
const url = split[0];
|
||||||
|
if (split.length > 1) {
|
||||||
|
return _.includes(url, '.') || _.includes(url, ':');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDigestFromRepository(repository) {
|
||||||
return repository.split('@sha')[0];
|
return repository.split('@sha')[0];
|
||||||
};
|
}
|
||||||
|
|
||||||
return helper;
|
return helper;
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* This model should be used with por-image-registry component
|
||||||
|
* And bound to the 'model' attribute
|
||||||
|
*
|
||||||
|
* // viewController.js
|
||||||
|
*
|
||||||
|
* this.imageModel = new PorImageRegistryModel();
|
||||||
|
*
|
||||||
|
* // view.html
|
||||||
|
* <por-image-registry model="$ctrl.imageModel" ... />
|
||||||
|
*/
|
||||||
|
export function PorImageRegistryModel() {
|
||||||
|
this.UseRegistry = true;
|
||||||
|
this.Registry = {};
|
||||||
|
this.Image = '';
|
||||||
|
}
|
|
@ -5,6 +5,6 @@ angular.module('portainer.docker')
|
||||||
endpointId: EndpointProvider.endpointID
|
endpointId: EndpointProvider.endpointID
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
commitContainer: {method: 'POST', params: {container: '@id', repo: '@repo', tag: '@tag'}, ignoreLoadingBar: true}
|
commitContainer: {method: 'POST', params: {container: '@id', repo: '@repo'}, ignoreLoadingBar: true}
|
||||||
});
|
});
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -15,16 +15,16 @@ function ImageFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider, HttpR
|
||||||
search: {method: 'GET', params: {action: 'search'}},
|
search: {method: 'GET', params: {action: 'search'}},
|
||||||
history: {method: 'GET', params: {action: 'history'}, isArray: true},
|
history: {method: 'GET', params: {action: 'history'}, isArray: true},
|
||||||
insert: {method: 'POST', params: {id: '@id', action: 'insert'}},
|
insert: {method: 'POST', params: {id: '@id', action: 'insert'}},
|
||||||
tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo', tag: '@tag'}, ignoreLoadingBar: true},
|
tag: {method: 'POST', params: {id: '@id', action: 'tag', force: 0, repo: '@repo'}, ignoreLoadingBar: true},
|
||||||
inspect: {method: 'GET', params: {id: '@id', action: 'json'}},
|
inspect: {method: 'GET', params: {id: '@id', action: 'json'}},
|
||||||
push: {
|
push: {
|
||||||
method: 'POST', params: {action: 'push', id: '@tag'},
|
method: 'POST', params: {action: 'push', id: '@imageName'},
|
||||||
isArray: true, transformResponse: jsonObjectsToArrayHandler,
|
isArray: true, transformResponse: jsonObjectsToArrayHandler,
|
||||||
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
||||||
ignoreLoadingBar: true
|
ignoreLoadingBar: true
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
method: 'POST', params: {action: 'create', fromImage: '@fromImage', tag: '@tag'},
|
method: 'POST', params: {action: 'create', fromImage: '@fromImage'},
|
||||||
isArray: true, transformResponse: jsonObjectsToArrayHandler,
|
isArray: true, transformResponse: jsonObjectsToArrayHandler,
|
||||||
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
headers: { 'X-Registry-Auth': HttpRequestHelper.registryAuthenticationHeader },
|
||||||
ignoreLoadingBar: true
|
ignoreLoadingBar: true
|
||||||
|
|
|
@ -77,12 +77,20 @@ angular.module('portainer.docker')
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
service.pushImage = function(tag, registry) {
|
service.pushImage = pushImage;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {PorImageRegistryModel} registryModel
|
||||||
|
*/
|
||||||
|
function pushImage(registryModel) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : '';
|
var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : '';
|
||||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||||
Image.push({tag: tag}).$promise
|
|
||||||
|
const imageConfiguration = ImageHelper.createImageConfigForContainer(registryModel);
|
||||||
|
|
||||||
|
Image.push({imageName: imageConfiguration.fromImage}).$promise
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
if (data[data.length - 1].error) {
|
if (data[data.length - 1].error) {
|
||||||
deferred.reject({ msg: data[data.length - 1].error });
|
deferred.reject({ msg: data[data.length - 1].error });
|
||||||
|
@ -94,7 +102,11 @@ angular.module('portainer.docker')
|
||||||
deferred.reject({ msg: 'Unable to push image tag', err: err });
|
deferred.reject({ msg: 'Unable to push image tag', err: err });
|
||||||
});
|
});
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PULL IMAGE
|
||||||
|
*/
|
||||||
|
|
||||||
function pullImageAndIgnoreErrors(imageConfiguration) {
|
function pullImageAndIgnoreErrors(imageConfiguration) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
@ -127,21 +139,31 @@ angular.module('portainer.docker')
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
service.pullImage = function(image, registry, ignoreErrors) {
|
service.pullImage = pullImage;
|
||||||
var imageDetails = ImageHelper.extractImageAndRegistryFromRepository(image);
|
|
||||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(imageDetails.image, registry.URL);
|
/**
|
||||||
var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : '';
|
*
|
||||||
|
* @param {PorImageRegistryModel} registry
|
||||||
|
* @param {bool} ignoreErrors
|
||||||
|
*/
|
||||||
|
function pullImage(registry, ignoreErrors) {
|
||||||
|
var authenticationDetails = registry.Registry.Authentication ? RegistryService.encodedCredentials(registry.Registry) : '';
|
||||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||||
|
|
||||||
|
var imageConfiguration = ImageHelper.createImageConfigForContainer(registry);
|
||||||
|
|
||||||
if (ignoreErrors) {
|
if (ignoreErrors) {
|
||||||
return pullImageAndIgnoreErrors(imageConfiguration);
|
return pullImageAndIgnoreErrors(imageConfiguration);
|
||||||
}
|
}
|
||||||
return pullImageAndAcknowledgeErrors(imageConfiguration);
|
return pullImageAndAcknowledgeErrors(imageConfiguration);
|
||||||
};
|
}
|
||||||
|
|
||||||
service.tagImage = function(id, image, registry) {
|
/**
|
||||||
var imageConfig = ImageHelper.createImageConfigForCommit(image, registry);
|
* ! PULL IMAGE
|
||||||
return Image.tag({id: id, tag: imageConfig.tag, repo: imageConfig.repo}).$promise;
|
*/
|
||||||
|
|
||||||
|
service.tagImage = function(id, image) {
|
||||||
|
return Image.tag({id: id, repo: image}).$promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
service.downloadImages = function(images) {
|
service.downloadImages = function(images) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash-es';
|
||||||
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
||||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
angular.module('portainer.docker')
|
||||||
|
@ -27,7 +28,8 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
|
||||||
NodeName: null,
|
NodeName: null,
|
||||||
capabilities: [],
|
capabilities: [],
|
||||||
LogDriverName: '',
|
LogDriverName: '',
|
||||||
LogDriverOpts: []
|
LogDriverOpts: [],
|
||||||
|
RegistryModel: new PorImageRegistryModel()
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.extraNetworks = {};
|
$scope.extraNetworks = {};
|
||||||
|
@ -130,11 +132,8 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
|
||||||
$scope.fromContainerMultipleNetworks = false;
|
$scope.fromContainerMultipleNetworks = false;
|
||||||
|
|
||||||
function prepareImageConfig(config) {
|
function prepareImageConfig(config) {
|
||||||
var image = config.Image;
|
const imageConfig = ImageHelper.createImageConfigForContainer($scope.formValues.RegistryModel);
|
||||||
var registry = $scope.formValues.Registry;
|
config.Image = imageConfig.fromImage;
|
||||||
var imageConfig = ImageHelper.createImageConfigForContainer(image, registry.URL);
|
|
||||||
config.Image = imageConfig.fromImage + ':' + imageConfig.tag;
|
|
||||||
$scope.imageConfig = imageConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function preparePortBindings(config) {
|
function preparePortBindings(config) {
|
||||||
|
@ -438,13 +437,9 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFromContainerImageConfig() {
|
function loadFromContainerImageConfig() {
|
||||||
var imageInfo = ImageHelper.extractImageAndRegistryFromRepository($scope.config.Image);
|
RegistryService.retrievePorRegistryModelFromRepository($scope.config.Image)
|
||||||
RegistryService.retrieveRegistryFromRepository($scope.config.Image)
|
.then((model) => {
|
||||||
.then(function success(data) {
|
$scope.formValues.RegistryModel = model;
|
||||||
if (data) {
|
|
||||||
$scope.config.Image = imageInfo.image;
|
|
||||||
$scope.formValues.Registry = data;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrive registry');
|
Notifications.error('Failure', err, 'Unable to retrive registry');
|
||||||
|
@ -569,7 +564,6 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
|
||||||
loadFromContainerSpec();
|
loadFromContainerSpec();
|
||||||
} else {
|
} else {
|
||||||
$scope.fromContainer = {};
|
$scope.fromContainer = {};
|
||||||
$scope.formValues.Registry = {};
|
|
||||||
$scope.formValues.capabilities = new ContainerCapabilities();
|
$scope.formValues.capabilities = new ContainerCapabilities();
|
||||||
}
|
}
|
||||||
}, function(e) {
|
}, function(e) {
|
||||||
|
@ -754,7 +748,7 @@ function ($q, $scope, $async, $state, $timeout, $transition$, $filter, Container
|
||||||
|
|
||||||
function pullImageIfNeeded() {
|
function pullImageIfNeeded() {
|
||||||
return $q.when($scope.formValues.alwaysPull &&
|
return $q.when($scope.formValues.alwaysPull &&
|
||||||
ImageService.pullImage($scope.config.Image, $scope.formValues.Registry, true));
|
ImageService.pullImage($scope.formValues.RegistryModel, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewContainer() {
|
function createNewContainer() {
|
||||||
|
|
|
@ -21,18 +21,18 @@
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Image configuration
|
Image configuration
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="!formValues.Registry && fromContainer">
|
<div ng-if="!formValues.RegistryModel.Registry && fromContainer">
|
||||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
|
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
|
||||||
<span class="small text-danger" style="margin-left: 5px;">The Docker registry for the <code>{{ config.Image }}</code> image is not registered inside Portainer, you will not be able to create a container. Please register that registry first.</span>
|
<span class="small text-danger" style="margin-left: 5px;">The Docker registry for the <code>{{ config.Image }}</code> image is not registered inside Portainer, you will not be able to create a container. Please register that registry first.</span>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="formValues.Registry || !fromContainer">
|
<div ng-if="formValues.RegistryModel.Registry || !fromContainer">
|
||||||
<!-- image-and-registry -->
|
<!-- image-and-registry -->
|
||||||
<por-image-registry
|
<por-image-registry
|
||||||
image="config.Image"
|
model="formValues.RegistryModel"
|
||||||
registry="formValues.Registry"
|
pull-warning="formValues.alwaysPull"
|
||||||
ng-if="formValues.Registry"
|
ng-if="formValues.RegistryModel.Registry"
|
||||||
auto-complete="true"
|
auto-complete="true"
|
||||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
label-class="col-sm-1" input-class="col-sm-11"
|
||||||
></por-image-registry>
|
></por-image-registry>
|
||||||
<!-- !image-and-registry -->
|
<!-- !image-and-registry -->
|
||||||
<!-- always-pull -->
|
<!-- always-pull -->
|
||||||
|
@ -142,7 +142,9 @@
|
||||||
<!-- !autoremove -->
|
<!-- !autoremove -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !config.Image || (!formValues.Registry && fromContainer)" ng-click="create()" button-spinner="state.actionInProgress">
|
<button type="button" class="btn btn-primary btn-sm"
|
||||||
|
ng-disabled="state.actionInProgress || !formValues.RegistryModel.Image || (!formValues.RegistryModel.Registry && fromContainer)"
|
||||||
|
ng-click="create()" button-spinner="state.actionInProgress">
|
||||||
<span ng-hide="state.actionInProgress">Deploy the container</span>
|
<span ng-hide="state.actionInProgress">Deploy the container</span>
|
||||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -155,10 +155,9 @@
|
||||||
<!-- !tag-description -->
|
<!-- !tag-description -->
|
||||||
<!-- image-and-registry -->
|
<!-- image-and-registry -->
|
||||||
<por-image-registry
|
<por-image-registry
|
||||||
image="config.Image"
|
model="config.RegistryModel"
|
||||||
registry="config.Registry"
|
|
||||||
auto-complete="true"
|
auto-complete="true"
|
||||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
label-class="col-sm-1" input-class="col-sm-11"
|
||||||
></por-image-registry>
|
></por-image-registry>
|
||||||
<!-- !image-and-registry -->
|
<!-- !image-and-registry -->
|
||||||
<!-- tag-note -->
|
<!-- tag-note -->
|
||||||
|
@ -170,7 +169,7 @@
|
||||||
<!-- !tag-note -->
|
<!-- !tag-note -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Image" ng-click="commit()">Create</button>
|
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.RegistryModel.Image || config.commitInProgress" ng-click="commit()">Create</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
angular.module('portainer.docker')
|
||||||
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', 'Commit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', 'HttpRequestHelper', 'Authentication',
|
.controller('ContainerController', ['$q', '$scope', '$state','$transition$', '$filter', '$async', 'Commit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'NetworkService', 'Notifications', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService', 'HttpRequestHelper', 'Authentication',
|
||||||
function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, ContainerService, ImageHelper, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService, HttpRequestHelper, Authentication) {
|
function ($q, $scope, $state, $transition$, $filter, $async, Commit, ContainerHelper, ContainerService, ImageHelper, NetworkService, Notifications, ModalService, ResourceControlService, RegistryService, ImageService, HttpRequestHelper, Authentication) {
|
||||||
$scope.activityTime = 0;
|
$scope.activityTime = 0;
|
||||||
$scope.portBindings = [];
|
$scope.portBindings = [];
|
||||||
|
|
||||||
$scope.config = {
|
$scope.config = {
|
||||||
Image: '',
|
RegistryModel: new PorImageRegistryModel(),
|
||||||
Registry: ''
|
commitInProgress: false
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
|
@ -149,20 +150,23 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.commit = function () {
|
async function commitContainerAsync() {
|
||||||
var image = $scope.config.Image;
|
$scope.config.commitInProgress = true;
|
||||||
$scope.config.Image = '';
|
const registryModel = $scope.config.RegistryModel;
|
||||||
var registry = $scope.config.Registry;
|
const imageConfig = ImageHelper.createImageConfigForContainer(registryModel);
|
||||||
var imageConfig = ImageHelper.createImageConfigForCommit(image, registry.URL);
|
try {
|
||||||
Commit.commitContainer({id: $transition$.params().id, tag: imageConfig.tag, repo: imageConfig.repo}, function () {
|
await Commit.commitContainer({id: $transition$.params().id, repo: imageConfig.fromImage}).$promise;
|
||||||
update();
|
|
||||||
Notifications.success('Image created', $transition$.params().id);
|
Notifications.success('Image created', $transition$.params().id);
|
||||||
}, function (e) {
|
$state.reload();
|
||||||
update();
|
} catch (err) {
|
||||||
Notifications.error('Failure', e, 'Unable to create image');
|
Notifications.error('Failure', err, 'Unable to create image');
|
||||||
});
|
$scope.config.commitInProgress = false;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.commit = function () {
|
||||||
|
return $async(commitContainerAsync);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.confirmRemove = function () {
|
$scope.confirmRemove = function () {
|
||||||
var title = 'You are about to remove a container.';
|
var title = 'You are about to remove a container.';
|
||||||
|
@ -225,15 +229,12 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co
|
||||||
if (!pullImage) {
|
if (!pullImage) {
|
||||||
return $q.when();
|
return $q.when();
|
||||||
}
|
}
|
||||||
return getRegistry().then(function pullImage(containerRegistery) {
|
return RegistryService.retrievePorRegistryModelFromRepository(container.Config.Image)
|
||||||
return ImageService.pullImage(container.Config.Image, containerRegistery, true);
|
.then(function pullImage(registryModel) {
|
||||||
|
return ImageService.pullImage(registryModel, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRegistry() {
|
|
||||||
return RegistryService.retrieveRegistryFromRepository(container.Config.Image);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMainNetworkAndCreateContainer() {
|
function setMainNetworkAndCreateContainer() {
|
||||||
var networks = config.NetworkingConfig.EndpointsConfig;
|
var networks = config.NetworkingConfig.EndpointsConfig;
|
||||||
var networksNames = Object.keys(networks);
|
var networksNames = Object.keys(networks);
|
||||||
|
|
|
@ -64,9 +64,8 @@
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<!-- image-and-registry -->
|
<!-- image-and-registry -->
|
||||||
<por-image-registry
|
<por-image-registry
|
||||||
image="formValues.Image"
|
model="formValues.RegistryModel"
|
||||||
registry="formValues.Registry"
|
label-class="col-sm-1" input-class="col-sm-11"
|
||||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
|
||||||
></por-image-registry>
|
></por-image-registry>
|
||||||
<!-- !image-and-registry -->
|
<!-- !image-and-registry -->
|
||||||
<!-- tag-note -->
|
<!-- tag-note -->
|
||||||
|
@ -78,7 +77,7 @@
|
||||||
<!-- !tag-note -->
|
<!-- !tag-note -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.Image" ng-click="tagImage()">Tag</button>
|
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.RegistryModel.Image" ng-click="tagImage()">Tag</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
angular.module('portainer.docker')
|
||||||
.controller('ImageController', ['$q', '$scope', '$transition$', '$state', '$timeout', 'ImageService', 'RegistryService', 'Notifications', 'HttpRequestHelper', 'ModalService', 'FileSaver', 'Blob',
|
.controller('ImageController', ['$q', '$scope', '$transition$', '$state', '$timeout', 'ImageService', 'ImageHelper', 'RegistryService', 'Notifications', 'HttpRequestHelper', 'ModalService', 'FileSaver', 'Blob',
|
||||||
function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryService, Notifications, HttpRequestHelper, ModalService, FileSaver, Blob) {
|
function ($q, $scope, $transition$, $state, $timeout, ImageService, ImageHelper, RegistryService, Notifications, HttpRequestHelper, ModalService, FileSaver, Blob) {
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
Image: '',
|
RegistryModel: new PorImageRegistryModel()
|
||||||
Registry: ''
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
|
@ -27,10 +27,11 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.tagImage = function() {
|
$scope.tagImage = function() {
|
||||||
var image = $scope.formValues.Image;
|
const registryModel = $scope.formValues.RegistryModel;
|
||||||
var registry = $scope.formValues.Registry;
|
|
||||||
|
|
||||||
ImageService.tagImage($transition$.params().id, image, registry.URL)
|
const image = ImageHelper.createImageConfigForContainer(registryModel);
|
||||||
|
|
||||||
|
ImageService.tagImage($transition$.params().id, image.fromImage)
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Image successfully tagged');
|
Notifications.success('Image successfully tagged');
|
||||||
$state.go('docker.images.image', {id: $transition$.params().id}, {reload: true});
|
$state.go('docker.images.image', {id: $transition$.params().id}, {reload: true});
|
||||||
|
@ -42,10 +43,9 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ
|
||||||
|
|
||||||
$scope.pushTag = function(repository) {
|
$scope.pushTag = function(repository) {
|
||||||
$('#uploadResourceHint').show();
|
$('#uploadResourceHint').show();
|
||||||
RegistryService.retrieveRegistryFromRepository(repository)
|
RegistryService.retrievePorRegistryModelFromRepository(repository)
|
||||||
.then(function success(data) {
|
.then(function success(registryModel) {
|
||||||
var registry = data;
|
return ImageService.pushImage(registryModel);
|
||||||
return ImageService.pushImage(repository, registry);
|
|
||||||
})
|
})
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Image successfully pushed', repository);
|
Notifications.success('Image successfully pushed', repository);
|
||||||
|
@ -60,10 +60,9 @@ function ($q, $scope, $transition$, $state, $timeout, ImageService, RegistryServ
|
||||||
|
|
||||||
$scope.pullTag = function(repository) {
|
$scope.pullTag = function(repository) {
|
||||||
$('#downloadResourceHint').show();
|
$('#downloadResourceHint').show();
|
||||||
RegistryService.retrieveRegistryFromRepository(repository)
|
RegistryService.retrievePorRegistryModelFromRepository(repository)
|
||||||
.then(function success(data) {
|
.then(function success(registryModel) {
|
||||||
var registry = data;
|
return ImageService.pullImage(registryModel, false);
|
||||||
return ImageService.pullImage(repository, registry, false);
|
|
||||||
})
|
})
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Image successfully pulled', repository);
|
Notifications.success('Image successfully pulled', repository);
|
||||||
|
|
|
@ -16,18 +16,12 @@
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<!-- image-and-registry -->
|
<!-- image-and-registry -->
|
||||||
<por-image-registry
|
<por-image-registry
|
||||||
image="formValues.Image"
|
model="formValues.RegistryModel"
|
||||||
registry="formValues.Registry"
|
auto-complete="true"
|
||||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
pull-warning="true"
|
||||||
|
label-class="col-sm-1" input-class="col-sm-11"
|
||||||
></por-image-registry>
|
></por-image-registry>
|
||||||
<!-- !image-and-registry -->
|
<!-- !image-and-registry -->
|
||||||
<!-- tag-note -->
|
|
||||||
<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>
|
|
||||||
<!-- !tag-note -->
|
|
||||||
<div ng-if="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
|
<div ng-if="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Deployment
|
Deployment
|
||||||
|
@ -40,7 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !formValues.Image" ng-click="pullImage()" button-spinner="state.actionInProgress">
|
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !formValues.RegistryModel.Image" ng-click="pullImage()" button-spinner="state.actionInProgress">
|
||||||
<span ng-hide="state.actionInProgress">Pull the image</span>
|
<span ng-hide="state.actionInProgress">Pull the image</span>
|
||||||
<span ng-show="state.actionInProgress">Download in progress...</span>
|
<span ng-show="state.actionInProgress">Download in progress...</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
angular.module('portainer.docker')
|
angular.module('portainer.docker')
|
||||||
.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'ModalService', 'HttpRequestHelper', 'FileSaver', 'Blob', 'EndpointProvider',
|
.controller('ImagesController', ['$scope', '$state', 'ImageService', 'Notifications', 'ModalService', 'HttpRequestHelper', 'FileSaver', 'Blob', 'EndpointProvider',
|
||||||
|
@ -9,22 +10,20 @@ function ($scope, $state, ImageService, Notifications, ModalService, HttpRequest
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
Image: '',
|
RegistryModel: new PorImageRegistryModel(),
|
||||||
Registry: '',
|
|
||||||
NodeName: null
|
NodeName: null
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.pullImage = function() {
|
$scope.pullImage = function() {
|
||||||
var image = $scope.formValues.Image;
|
const registryModel = $scope.formValues.RegistryModel;
|
||||||
var registry = $scope.formValues.Registry;
|
|
||||||
|
|
||||||
var nodeName = $scope.formValues.NodeName;
|
var nodeName = $scope.formValues.NodeName;
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||||
|
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
ImageService.pullImage(image, registry, false)
|
ImageService.pullImage(registryModel, false)
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Image successfully pulled', image);
|
Notifications.success('Image successfully pulled', registryModel.Image);
|
||||||
$state.reload();
|
$state.reload();
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
require('./includes/update-restart.html')
|
require('./includes/update-restart.html')
|
||||||
require('./includes/secret.html')
|
require('./includes/secret.html')
|
||||||
|
@ -12,8 +13,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
Name: '',
|
Name: '',
|
||||||
Image: '',
|
RegistryModel: new PorImageRegistryModel(),
|
||||||
Registry: {},
|
|
||||||
Mode: 'replicated',
|
Mode: 'replicated',
|
||||||
Replicas: 1,
|
Replicas: 1,
|
||||||
Command: '',
|
Command: '',
|
||||||
|
@ -160,8 +160,8 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||||
};
|
};
|
||||||
|
|
||||||
function prepareImageConfig(config, input) {
|
function prepareImageConfig(config, input) {
|
||||||
var imageConfig = ImageHelper.createImageConfigForContainer(input.Image, input.Registry.URL);
|
var imageConfig = ImageHelper.createImageConfigForContainer(input.RegistryModel);
|
||||||
config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage + ':' + imageConfig.tag;
|
config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
function preparePortsConfig(config, input) {
|
function preparePortsConfig(config, input) {
|
||||||
|
@ -427,9 +427,8 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewService(config, accessControlData) {
|
function createNewService(config, accessControlData) {
|
||||||
|
const registryModel = $scope.formValues.RegistryModel;
|
||||||
var registry = $scope.formValues.Registry;
|
var authenticationDetails = registryModel.Registry.Authentication ? RegistryService.encodedCredentials(registryModel.Registry) : '';
|
||||||
var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : '';
|
|
||||||
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
|
||||||
|
|
||||||
Service.create(config).$promise
|
Service.create(config).$promise
|
||||||
|
|
|
@ -23,10 +23,9 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- image-and-registry -->
|
<!-- image-and-registry -->
|
||||||
<por-image-registry
|
<por-image-registry
|
||||||
image="formValues.Image"
|
model="formValues.RegistryModel"
|
||||||
registry="formValues.Registry"
|
|
||||||
auto-complete="true"
|
auto-complete="true"
|
||||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
label-class="col-sm-1" input-class="col-sm-11"
|
||||||
></por-image-registry>
|
></por-image-registry>
|
||||||
<!-- !image-and-registry -->
|
<!-- !image-and-registry -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
@ -126,7 +125,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !formValues.Image" ng-click="create()" button-spinner="state.actionInProgress">
|
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !formValues.RegistryModel.Image" ng-click="create()" button-spinner="state.actionInProgress">
|
||||||
<span ng-hide="state.actionInProgress">Create the service</span>
|
<span ng-hide="state.actionInProgress">Create the service</span>
|
||||||
<span ng-show="state.actionInProgress">Creating service...</span>
|
<span ng-show="state.actionInProgress">Creating service...</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<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"
|
||||||
|
></por-image-registry>
|
||||||
|
</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
|
||||||
|
|
|
@ -1,58 +1,6 @@
|
||||||
angular.module('portainer.app').component('templateForm', {
|
angular.module('portainer.app').component('templateForm', {
|
||||||
templateUrl: './templateForm.html',
|
templateUrl: './templateForm.html',
|
||||||
controller: function() {
|
controller: 'TemplateFormController',
|
||||||
this.state = {
|
|
||||||
collapseTemplate: false,
|
|
||||||
collapseContainer: false,
|
|
||||||
collapseStack: false,
|
|
||||||
collapseEnv: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addPortBinding = function() {
|
|
||||||
this.model.Ports.push({ containerPort: '', protocol: 'tcp' });
|
|
||||||
};
|
|
||||||
|
|
||||||
this.removePortBinding = function(index) {
|
|
||||||
this.model.Ports.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addVolume = function () {
|
|
||||||
this.model.Volumes.push({ container: '', bind: '', readonly: false, type: 'auto' });
|
|
||||||
};
|
|
||||||
|
|
||||||
this.removeVolume = function(index) {
|
|
||||||
this.model.Volumes.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addLabel = function () {
|
|
||||||
this.model.Labels.push({ name: '', value: ''});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.removeLabel = function(index) {
|
|
||||||
this.model.Labels.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addEnvVar = function() {
|
|
||||||
this.model.Env.push({ type: 1, name: '', label: '', description: '', default: '', preset: true, select: [] });
|
|
||||||
};
|
|
||||||
|
|
||||||
this.removeEnvVar = function(index) {
|
|
||||||
this.model.Env.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addEnvVarValue = function(env) {
|
|
||||||
env.select = env.select || [];
|
|
||||||
env.select.push({ name: '', value: '' });
|
|
||||||
};
|
|
||||||
|
|
||||||
this.removeEnvVarValue = function(env, index) {
|
|
||||||
env.select.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.changeEnvVarType = function(env) {
|
|
||||||
env.preset = env.type === 1;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
bindings: {
|
bindings: {
|
||||||
model: '=',
|
model: '=',
|
||||||
categories: '<',
|
categories: '<',
|
||||||
|
|
|
@ -203,10 +203,9 @@
|
||||||
<!-- container-details -->
|
<!-- container-details -->
|
||||||
<div uib-collapse="$ctrl.state.collapseContainer">
|
<div uib-collapse="$ctrl.state.collapseContainer">
|
||||||
<por-image-registry
|
<por-image-registry
|
||||||
image="$ctrl.model.Image"
|
model="$ctrl.model.RegistryModel"
|
||||||
registry="$ctrl.model.Registry"
|
|
||||||
auto-complete="true"
|
auto-complete="true"
|
||||||
label-class="col-sm-2" input-class="col-sm-10 col-md-4"
|
label-class="col-sm-1" input-class="col-sm-11"
|
||||||
></por-image-registry>
|
></por-image-registry>
|
||||||
<!-- command -->
|
<!-- command -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
angular.module('portainer.app')
|
||||||
|
.controller('TemplateFormController', [function() {
|
||||||
|
this.state = {
|
||||||
|
collapseTemplate: false,
|
||||||
|
collapseContainer: false,
|
||||||
|
collapseStack: false,
|
||||||
|
collapseEnv: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addPortBinding = function() {
|
||||||
|
this.model.Ports.push({ containerPort: '', protocol: 'tcp' });
|
||||||
|
};
|
||||||
|
|
||||||
|
this.removePortBinding = function(index) {
|
||||||
|
this.model.Ports.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addVolume = function () {
|
||||||
|
this.model.Volumes.push({ container: '', bind: '', readonly: false, type: 'auto' });
|
||||||
|
};
|
||||||
|
|
||||||
|
this.removeVolume = function(index) {
|
||||||
|
this.model.Volumes.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addLabel = function () {
|
||||||
|
this.model.Labels.push({ name: '', value: ''});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.removeLabel = function(index) {
|
||||||
|
this.model.Labels.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addEnvVar = function() {
|
||||||
|
this.model.Env.push({ type: 1, name: '', label: '', description: '', default: '', preset: true, select: [] });
|
||||||
|
};
|
||||||
|
|
||||||
|
this.removeEnvVar = function(index) {
|
||||||
|
this.model.Env.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addEnvVarValue = function(env) {
|
||||||
|
env.select = env.select || [];
|
||||||
|
env.select.push({ name: '', value: '' });
|
||||||
|
};
|
||||||
|
|
||||||
|
this.removeEnvVarValue = function(env, index) {
|
||||||
|
env.select.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.changeEnvVarType = function(env) {
|
||||||
|
env.preset = env.type === 1;
|
||||||
|
};
|
||||||
|
}]);
|
|
@ -1,18 +0,0 @@
|
||||||
angular.module('portainer.app')
|
|
||||||
.factory('RegistryHelper', [function RegistryHelperFactory() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var helper = {};
|
|
||||||
|
|
||||||
helper.getRegistryByURL = function(registries, url) {
|
|
||||||
for (var i = 0; i < registries.length; i++) {
|
|
||||||
if (registries[i].URL === url) {
|
|
||||||
return registries[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return helper;
|
|
||||||
}]);
|
|
|
@ -59,7 +59,8 @@ export function RegistryCreateRequest(model) {
|
||||||
if (model.Type === RegistryTypes.GITLAB) {
|
if (model.Type === RegistryTypes.GITLAB) {
|
||||||
this.Gitlab = {
|
this.Gitlab = {
|
||||||
ProjectId: model.Gitlab.ProjectId,
|
ProjectId: model.Gitlab.ProjectId,
|
||||||
InstanceURL: model.Gitlab.InstanceURL
|
InstanceURL: model.Gitlab.InstanceURL,
|
||||||
|
ProjectPath: model.Gitlab.ProjectPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
export function TemplateDefaultModel() {
|
export function TemplateDefaultModel() {
|
||||||
this.Type = 1;
|
this.Type = 1;
|
||||||
this.AdministratorOnly = false;
|
this.AdministratorOnly = false;
|
||||||
this.Title = '';
|
this.Title = '';
|
||||||
this.Image = '';
|
|
||||||
this.Description = '';
|
this.Description = '';
|
||||||
this.Volumes = [];
|
this.Volumes = [];
|
||||||
this.Ports = [];
|
this.Ports = [];
|
||||||
this.Env = [];
|
this.Env = [];
|
||||||
this.Labels = [];
|
this.Labels = [];
|
||||||
this.RestartPolicy = 'always';
|
this.RestartPolicy = 'always';
|
||||||
this.Registry = {};
|
this.RegistryModel = new PorImageRegistryModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TemplateCreateRequest(model) {
|
export function TemplateCreateRequest(model) {
|
||||||
|
@ -24,8 +24,8 @@ export function TemplateCreateRequest(model) {
|
||||||
this.Categories = model.Categories;
|
this.Categories = model.Categories;
|
||||||
this.Platform = model.Platform;
|
this.Platform = model.Platform;
|
||||||
this.Logo = model.Logo;
|
this.Logo = model.Logo;
|
||||||
this.Image = model.Image;
|
this.Image = model.RegistryModel.Image;
|
||||||
this.Registry = model.Registry.URL;
|
this.Registry = model.RegistryModel.Registry.URL;
|
||||||
this.Command = model.Command;
|
this.Command = model.Command;
|
||||||
this.Network = model.Network && model.Network.Name;
|
this.Network = model.Network && model.Network.Name;
|
||||||
this.Privileged = model.Privileged;
|
this.Privileged = model.Privileged;
|
||||||
|
@ -66,9 +66,9 @@ export function TemplateViewModel(data) {
|
||||||
this.Logo = data.logo;
|
this.Logo = data.logo;
|
||||||
this.Repository = data.repository;
|
this.Repository = data.repository;
|
||||||
this.Hostname = data.hostname;
|
this.Hostname = data.hostname;
|
||||||
this.Registry = data.registry ? { URL: data.registry } : {};
|
this.RegistryModel = new PorImageRegistryModel();
|
||||||
this.Image = data.image;
|
this.RegistryModel.Image = data.image;
|
||||||
this.Registry = data.registry ? data.registry : '';
|
this.RegistryModel.Registry.URL = data.registry || '';
|
||||||
this.Command = data.command ? data.command : '';
|
this.Command = data.command ? data.command : '';
|
||||||
this.Network = data.network ? data.network : '';
|
this.Network = data.network ? data.network : '';
|
||||||
this.Privileged = data.privileged ? data.privileged : false;
|
this.Privileged = data.privileged ? data.privileged : false;
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { RegistryViewModel, RegistryCreateRequest } from '../../models/registry';
|
import { RegistryViewModel, RegistryCreateRequest } from '../../models/registry';
|
||||||
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
import { RegistryTypes } from 'Extensions/registry-management/models/registryTypes';
|
||||||
|
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.factory('RegistryService', ['$q', 'Registries', 'DockerHubService', 'RegistryHelper', 'ImageHelper', 'FileUploadService', function RegistryServiceFactory($q, Registries, DockerHubService, RegistryHelper, ImageHelper, FileUploadService) {
|
.factory('RegistryService', ['$q', '$async', 'Registries', 'DockerHubService', 'ImageHelper', 'FileUploadService',
|
||||||
|
function RegistryServiceFactory($q, $async, Registries, DockerHubService, ImageHelper, FileUploadService) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var service = {};
|
var service = {};
|
||||||
|
|
||||||
|
@ -71,6 +74,7 @@ angular.module('portainer.app')
|
||||||
_.forEach(projects, (p) => {
|
_.forEach(projects, (p) => {
|
||||||
const m = model;
|
const m = model;
|
||||||
m.Name = p.PathWithNamespace;
|
m.Name = p.PathWithNamespace;
|
||||||
|
m.Gitlab.ProjectPath = _.toLower(p.PathWithNamespace);
|
||||||
m.Gitlab.ProjectId = p.Id;
|
m.Gitlab.ProjectId = p.Id;
|
||||||
m.Password = m.Token;
|
m.Password = m.Token;
|
||||||
const payload = new RegistryCreateRequest(m);
|
const payload = new RegistryCreateRequest(m);
|
||||||
|
@ -79,23 +83,52 @@ angular.module('portainer.app')
|
||||||
return $q.all(promises);
|
return $q.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
service.retrieveRegistryFromRepository = function(repository) {
|
service.retrievePorRegistryModelFromRepositoryWithRegistries = retrievePorRegistryModelFromRepositoryWithRegistries;
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
var imageDetails = ImageHelper.extractImageAndRegistryFromRepository(repository);
|
function getURL(reg) {
|
||||||
$q.when(imageDetails.registry ? service.registries() : DockerHubService.dockerhub())
|
let url = reg.URL;
|
||||||
.then(function success(data) {
|
if (reg.Type === RegistryTypes.GITLAB) {
|
||||||
var registry = data;
|
url = reg.URL + '/' + reg.Gitlab.ProjectPath;
|
||||||
if (imageDetails.registry) {
|
}
|
||||||
registry = RegistryHelper.getRegistryByURL(data, imageDetails.registry);
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, dockerhub) {
|
||||||
|
const model = new PorImageRegistryModel();
|
||||||
|
const registry = _.find(registries, (reg) => _.includes(repository, getURL(reg)));
|
||||||
|
if (registry) {
|
||||||
|
const url = getURL(registry);
|
||||||
|
const lastIndex = repository.lastIndexOf(url) + url.length;
|
||||||
|
let image = repository.substring(lastIndex);
|
||||||
|
if (!_.startsWith(image, ':')) {
|
||||||
|
image = image.substring(1);
|
||||||
}
|
}
|
||||||
deferred.resolve(registry);
|
model.Registry = registry;
|
||||||
})
|
model.Image = image;
|
||||||
.catch(function error(err) {
|
} else {
|
||||||
deferred.reject({ msg: 'Unable to retrieve the registry associated to the repository', err: err });
|
if (ImageHelper.imageContainsURL(repository)) {
|
||||||
});
|
model.UseRegistry = false;
|
||||||
|
}
|
||||||
|
model.Registry = dockerhub;
|
||||||
|
model.Image = repository;
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
return deferred.promise;
|
async function retrievePorRegistryModelFromRepositoryAsync(repository) {
|
||||||
|
try {
|
||||||
|
let [registries, dockerhub] = await Promise.all([
|
||||||
|
service.registries(),
|
||||||
|
DockerHubService.dockerhub()
|
||||||
|
]);
|
||||||
|
return retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, dockerhub);
|
||||||
|
} catch (err) {
|
||||||
|
throw { msg: 'Unable to retrieve the registry associated to the repository', err: err }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service.retrievePorRegistryModelFromRepository = function(repository) {
|
||||||
|
return $async(retrievePorRegistryModelFromRepositoryAsync, repository)
|
||||||
};
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
|
@ -5,18 +5,26 @@ import {
|
||||||
} from '../../models/template';
|
} from '../../models/template';
|
||||||
|
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.factory('TemplateService', ['$q', 'Templates', 'TemplateHelper', 'ImageHelper', 'ContainerHelper',
|
.factory('TemplateService', ['$q', 'Templates', 'TemplateHelper', 'RegistryService', 'DockerHubService', 'ImageHelper', 'ContainerHelper',
|
||||||
function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, ContainerHelper) {
|
function TemplateServiceFactory($q, Templates, TemplateHelper, RegistryService, DockerHubService, ImageHelper, ContainerHelper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var service = {};
|
var service = {};
|
||||||
|
|
||||||
service.templates = function() {
|
service.templates = function() {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
Templates.query().$promise
|
$q.all({
|
||||||
|
templates: Templates.query().$promise,
|
||||||
|
registries: RegistryService.registries(),
|
||||||
|
dockerhub: DockerHubService.dockerhub()
|
||||||
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var templates = data.map(function (item) {
|
const templates = data.templates.map(function (item) {
|
||||||
return new TemplateViewModel(item);
|
const res = new TemplateViewModel(item);
|
||||||
|
const registry = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(res.RegistryModel.Registry.URL, data.registries, data.dockerhub);
|
||||||
|
registry.Image = res.RegistryModel.Image;
|
||||||
|
res.RegistryModel = registry;
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
deferred.resolve(templates);
|
deferred.resolve(templates);
|
||||||
})
|
})
|
||||||
|
@ -29,10 +37,15 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
|
||||||
|
|
||||||
service.template = function(id) {
|
service.template = function(id) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
let template;
|
||||||
Templates.get({ id: id }).$promise
|
Templates.get({ id: id }).$promise
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var template = new TemplateViewModel(data);
|
template = new TemplateViewModel(data);
|
||||||
|
return RegistryService.retrievePorRegistryModelFromRepository(template.RegistryModel.Registry.URL);
|
||||||
|
})
|
||||||
|
.then((registry) => {
|
||||||
|
registry.Image = template.RegistryModel.Image;
|
||||||
|
template.RegistryModel = registry;
|
||||||
deferred.resolve(template);
|
deferred.resolve(template);
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
|
@ -58,13 +71,13 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createTemplateConfiguration = function(template, containerName, network) {
|
service.createTemplateConfiguration = function(template, containerName, network) {
|
||||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.Image, template.Registry);
|
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
|
||||||
var containerConfiguration = service.createContainerConfiguration(template, containerName, network);
|
var containerConfiguration = createContainerConfiguration(template, containerName, network);
|
||||||
containerConfiguration.Image = imageConfiguration.fromImage + ':' + imageConfiguration.tag;
|
containerConfiguration.Image = imageConfiguration.fromImage;
|
||||||
return containerConfiguration;
|
return containerConfiguration;
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createContainerConfiguration = function(template, containerName, network) {
|
function createContainerConfiguration(template, containerName, network) {
|
||||||
var configuration = TemplateHelper.getDefaultContainerConfiguration();
|
var configuration = TemplateHelper.getDefaultContainerConfiguration();
|
||||||
configuration.HostConfig.NetworkMode = network.Name;
|
configuration.HostConfig.NetworkMode = network.Name;
|
||||||
configuration.HostConfig.Privileged = template.Privileged;
|
configuration.HostConfig.Privileged = template.Privileged;
|
||||||
|
@ -72,7 +85,6 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
|
||||||
configuration.HostConfig.ExtraHosts = template.Hosts ? template.Hosts : [];
|
configuration.HostConfig.ExtraHosts = template.Hosts ? template.Hosts : [];
|
||||||
configuration.name = containerName;
|
configuration.name = containerName;
|
||||||
configuration.Hostname = template.Hostname;
|
configuration.Hostname = template.Hostname;
|
||||||
configuration.Image = template.Image;
|
|
||||||
configuration.Env = TemplateHelper.EnvToStringArray(template.Env);
|
configuration.Env = TemplateHelper.EnvToStringArray(template.Env);
|
||||||
configuration.Cmd = ContainerHelper.commandStringToArray(template.Command);
|
configuration.Cmd = ContainerHelper.commandStringToArray(template.Command);
|
||||||
var portConfiguration = TemplateHelper.portArrayToPortConfiguration(template.Ports);
|
var portConfiguration = TemplateHelper.portArrayToPortConfiguration(template.Ports);
|
||||||
|
@ -83,7 +95,7 @@ function TemplateServiceFactory($q, Templates, TemplateHelper, ImageHelper, Cont
|
||||||
configuration.Tty = consoleConfiguration.tty;
|
configuration.Tty = consoleConfiguration.tty;
|
||||||
configuration.Labels = TemplateHelper.updateContainerConfigurationWithLabels(template.Labels);
|
configuration.Labels = TemplateHelper.updateContainerConfigurationWithLabels(template.Labels);
|
||||||
return configuration;
|
return configuration;
|
||||||
};
|
}
|
||||||
|
|
||||||
service.updateContainerConfigurationWithVolumes = function(configuration, template, generatedVolumesPile) {
|
service.updateContainerConfigurationWithVolumes = function(configuration, template, generatedVolumesPile) {
|
||||||
var volumes = template.Volumes;
|
var volumes = template.Volumes;
|
||||||
|
|
|
@ -73,7 +73,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
|
||||||
generatedVolumeIds.push(volumeId);
|
generatedVolumeIds.push(volumeId);
|
||||||
});
|
});
|
||||||
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
|
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
|
||||||
return ImageService.pullImage(template.Image, { URL: template.Registry }, true);
|
return ImageService.pullImage(template.RegistryModel, true);
|
||||||
})
|
})
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
return ContainerService.createAndStartContainer(templateConfiguration);
|
return ContainerService.createAndStartContainer(templateConfiguration);
|
||||||
|
@ -112,7 +112,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
|
||||||
var endpointId = EndpointProvider.endpointID();
|
var endpointId = EndpointProvider.endpointID();
|
||||||
StackService.createComposeStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId)
|
StackService.createComposeStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
const resourceControl = data.Portainer.ResourceControl;
|
const resourceControl = data.ResourceControl;
|
||||||
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
||||||
})
|
})
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
|
@ -150,7 +150,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
|
||||||
var endpointId = EndpointProvider.endpointID();
|
var endpointId = EndpointProvider.endpointID();
|
||||||
StackService.createSwarmStackFromGitRepository(stackName, repositoryOptions, env, endpointId)
|
StackService.createSwarmStackFromGitRepository(stackName, repositoryOptions, env, endpointId)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
const resourceControl = data.Portainer.ResourceControl;
|
const resourceControl = data.ResourceControl;
|
||||||
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
||||||
})
|
})
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
|
@ -158,7 +158,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, ContainerService, Ima
|
||||||
$state.go('portainer.stacks');
|
$state.go('portainer.stacks');
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.warning('Deployment error', err.err.data.err);
|
Notifications.warning('Deployment error', err.data.err);
|
||||||
})
|
})
|
||||||
.finally(function final() {
|
.finally(function final() {
|
||||||
$scope.state.actionInProgress = false;
|
$scope.state.actionInProgress = false;
|
||||||
|
|
|
@ -514,7 +514,7 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(min-width: 768px) and (max-width: 992px) {
|
@media(min-width: 768px){
|
||||||
.margin-sm-top {
|
.margin-sm-top {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue