mirror of https://github.com/portainer/portainer
feat(container-details): add the ability to re-create, duplicate and edit a container (#855)
parent
d814f3aaa4
commit
c85aa0739d
|
@ -244,7 +244,7 @@ angular.module('portainer', [
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('actions.create.container', {
|
.state('actions.create.container', {
|
||||||
url: '/container',
|
url: '/container/:from',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
templateUrl: 'app/components/createContainer/createcontainer.html',
|
templateUrl: 'app/components/createContainer/createcontainer.html',
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
<button class="btn btn-primary" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
|
<button class="btn btn-primary" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
|
||||||
<button class="btn btn-primary" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
|
<button class="btn btn-primary" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
|
||||||
<button class="btn btn-danger" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
<button class="btn btn-danger" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||||
|
<button class="btn btn-danger" ng-click="recreate()"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</button>
|
||||||
|
<button class="btn btn-primary" ng-click="duplicate()"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button>
|
||||||
</div>
|
</div>
|
||||||
</rd-widget-body>
|
</rd-widget-body>
|
||||||
</rd-widget>
|
</rd-widget>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
angular.module('container', [])
|
angular.module('container', [])
|
||||||
.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'Pagination', 'ModalService',
|
.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ContainerHelper', 'ContainerService', 'ImageHelper', 'Network', 'NetworkService', 'Notifications', 'Pagination', 'ModalService', 'ResourceControlService', 'RegistryService', 'ImageService',
|
||||||
function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ContainerService, ImageHelper, Network, NetworkService, Notifications, Pagination, ModalService) {
|
function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ContainerHelper, ContainerService, ImageHelper, Network, NetworkService, Notifications, Pagination, ModalService, ResourceControlService, RegistryService, ImageService) {
|
||||||
$scope.activityTime = 0;
|
$scope.activityTime = 0;
|
||||||
$scope.portBindings = [];
|
$scope.portBindings = [];
|
||||||
$scope.config = {
|
$scope.config = {
|
||||||
|
@ -196,6 +196,73 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Con
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.duplicate = function() {
|
||||||
|
ModalService.confirmExperimentalFeature(function (experimental) {
|
||||||
|
if(!experimental) { return; }
|
||||||
|
$state.go('actions.create.container', {from: $stateParams.id}, {reload: true});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.recreate = function() {
|
||||||
|
ModalService.confirmExperimentalFeature(function (experimental) {
|
||||||
|
if(!experimental) { return; }
|
||||||
|
ModalService.confirm({
|
||||||
|
title: 'Are you sure ?',
|
||||||
|
message: 'You\'re about to re-create this container, any non-persisted data will be lost. This container will be removed and another one will be created using the same configuration.',
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: 'Recreate',
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function onConfirm(confirmed) {
|
||||||
|
if(!confirmed) { return; }
|
||||||
|
else {
|
||||||
|
$('#loadingViewSpinner').show();
|
||||||
|
var container = $scope.container;
|
||||||
|
var config = ContainerHelper.configFromContainer(container.Model);
|
||||||
|
ContainerService.remove(container, true)
|
||||||
|
.then(function success() {
|
||||||
|
return RegistryService.retrieveRegistryFromRepository(container.Config.Image);
|
||||||
|
})
|
||||||
|
.then(function success(data) {
|
||||||
|
return ImageService.pullImage(container.Config.Image, data, true);
|
||||||
|
})
|
||||||
|
.then(function success() {
|
||||||
|
return ContainerService.createAndStartContainer(config);
|
||||||
|
})
|
||||||
|
.then(function success(data) {
|
||||||
|
if (!container.ResourceControl) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
var containerIdentifier = data.Id;
|
||||||
|
var resourceControl = container.ResourceControl;
|
||||||
|
var users = resourceControl.UserAccesses.map(function(u) {
|
||||||
|
return u.UserId;
|
||||||
|
});
|
||||||
|
var teams = resourceControl.TeamAccesses.map(function(t) {
|
||||||
|
return t.TeamId;
|
||||||
|
});
|
||||||
|
return ResourceControlService.createResourceControl(resourceControl.AdministratorsOnly,
|
||||||
|
users, teams, containerIdentifier, 'container', []);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function success(data) {
|
||||||
|
Notifications.success('Container successfully re-created');
|
||||||
|
$state.go('containers', {}, {reload: true});
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to re-create container');
|
||||||
|
})
|
||||||
|
.finally(function final() {
|
||||||
|
$('#loadingViewSpinner').hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) {
|
$scope.containerJoinNetwork = function containerJoinNetwork(container, networkId) {
|
||||||
$('#joinNetworkSpinner').show();
|
$('#joinNetworkSpinner').show();
|
||||||
Network.connect({id: networkId}, { Container: $stateParams.id }, function (d) {
|
Network.connect({id: networkId}, { Container: $stateParams.id }, function (d) {
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||||
// See app/components/templates/templatesController.js as a reference.
|
// See app/components/templates/templatesController.js as a reference.
|
||||||
angular.module('createContainer', [])
|
angular.module('createContainer', [])
|
||||||
.controller('CreateContainerController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator',
|
.controller('CreateContainerController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', 'ModalService', 'RegistryService',
|
||||||
function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator) {
|
function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService) {
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
alwaysPull: true,
|
alwaysPull: true,
|
||||||
Console: 'none',
|
Console: 'none',
|
||||||
Volumes: [],
|
Volumes: [],
|
||||||
Registry: '',
|
|
||||||
NetworkContainer: '',
|
NetworkContainer: '',
|
||||||
Labels: [],
|
Labels: [],
|
||||||
ExtraHosts: [],
|
ExtraHosts: [],
|
||||||
|
@ -92,6 +91,8 @@ function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper,
|
||||||
$scope.config.HostConfig.Devices.splice(index, 1);
|
$scope.config.HostConfig.Devices.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.fromContainerMultipleNetworks = false;
|
||||||
|
|
||||||
function prepareImageConfig(config) {
|
function prepareImageConfig(config) {
|
||||||
var image = config.Image;
|
var image = config.Image;
|
||||||
var registry = $scope.formValues.Registry;
|
var registry = $scope.formValues.Registry;
|
||||||
|
@ -179,6 +180,7 @@ function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper,
|
||||||
var networkMode = mode;
|
var networkMode = mode;
|
||||||
if (containerName) {
|
if (containerName) {
|
||||||
networkMode += ':' + containerName;
|
networkMode += ':' + containerName;
|
||||||
|
config.Hostname = '';
|
||||||
}
|
}
|
||||||
config.HostConfig.NetworkMode = networkMode;
|
config.HostConfig.NetworkMode = networkMode;
|
||||||
|
|
||||||
|
@ -233,6 +235,213 @@ function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper,
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function confirmCreateContainer() {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Container.query({ all: 1, filters: {name: ['^/' + $scope.config.name + '$'] }}).$promise
|
||||||
|
.then(function success(data) {
|
||||||
|
var existingContainer = data[0];
|
||||||
|
if (existingContainer) {
|
||||||
|
ModalService.confirm({
|
||||||
|
title: 'Are you sure ?',
|
||||||
|
message: 'A container with the same name already exists. Portainer can automatically remove it and re-create one. Do you want to replace it?',
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: 'Replace',
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function onConfirm(confirmed) {
|
||||||
|
if(!confirmed) { deferred.resolve(false); }
|
||||||
|
else {
|
||||||
|
// Remove old container
|
||||||
|
ContainerService.remove(existingContainer, true)
|
||||||
|
.then(function success(data) {
|
||||||
|
Notifications.success('Container Removed', existingContainer.Id);
|
||||||
|
deferred.resolve(true);
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject({ msg: 'Unable to remove container', err: err });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
deferred.resolve(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to retrieve containers');
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerCmd(d) {
|
||||||
|
if ($scope.config.Cmd) {
|
||||||
|
$scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd);
|
||||||
|
} else {
|
||||||
|
$scope.config.Cmd = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerPortBindings(d) {
|
||||||
|
var bindings = [];
|
||||||
|
for (var p in $scope.config.HostConfig.PortBindings) {
|
||||||
|
if ({}.hasOwnProperty.call($scope.config.HostConfig.PortBindings, p)) {
|
||||||
|
var hostPort = '';
|
||||||
|
if ($scope.config.HostConfig.PortBindings[p][0].HostIp) {
|
||||||
|
hostPort = $scope.config.HostConfig.PortBindings[p][0].HostIp + ':';
|
||||||
|
}
|
||||||
|
hostPort += $scope.config.HostConfig.PortBindings[p][0].HostPort;
|
||||||
|
var b = {
|
||||||
|
'hostPort': hostPort,
|
||||||
|
'containerPort': p.split('/')[0],
|
||||||
|
'protocol': p.split('/')[1]
|
||||||
|
};
|
||||||
|
bindings.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.config.HostConfig.PortBindings = bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerVolumes(d) {
|
||||||
|
for (var v in d.Mounts) {
|
||||||
|
if ({}.hasOwnProperty.call(d.Mounts, v)) {
|
||||||
|
var mount = d.Mounts[v];
|
||||||
|
var volume = {
|
||||||
|
'type': mount.Type,
|
||||||
|
'name': mount.Name || mount.Source,
|
||||||
|
'containerPath': mount.Destination,
|
||||||
|
'readOnly': mount.RW === false
|
||||||
|
};
|
||||||
|
$scope.formValues.Volumes.push(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerNetworkConfig(d) {
|
||||||
|
$scope.config.NetworkingConfig = {
|
||||||
|
EndpointsConfig: {}
|
||||||
|
};
|
||||||
|
var networkMode = d.HostConfig.NetworkMode;
|
||||||
|
if (networkMode === 'default') {
|
||||||
|
$scope.config.HostConfig.NetworkMode = 'bridge';
|
||||||
|
if (!_.find($scope.availableNetworks, {'Name': 'bridge'})) {
|
||||||
|
$scope.config.HostConfig.NetworkMode = 'nat';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($scope.config.HostConfig.NetworkMode.indexOf('container:') === 0) {
|
||||||
|
var netContainer = $scope.config.HostConfig.NetworkMode.split(/^container:/)[1];
|
||||||
|
$scope.config.HostConfig.NetworkMode = 'container';
|
||||||
|
for (var c in $scope.runningContainers) {
|
||||||
|
if ($scope.runningContainers[c].Names && $scope.runningContainers[c].Names[0] === '/' + netContainer) {
|
||||||
|
$scope.formValues.NetworkContainer = $scope.runningContainers[c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.fromContainerMultipleNetworks = Object.keys(d.NetworkSettings.Networks).length >= 2;
|
||||||
|
if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode]) {
|
||||||
|
if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig) {
|
||||||
|
if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address) {
|
||||||
|
$scope.formValues.IPv4 = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address;
|
||||||
|
}
|
||||||
|
if (d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address) {
|
||||||
|
$scope.formValues.IPv6 = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.config.NetworkingConfig.EndpointsConfig[$scope.config.HostConfig.NetworkMode] = d.NetworkSettings.Networks[$scope.config.HostConfig.NetworkMode];
|
||||||
|
// ExtraHosts
|
||||||
|
for (var h in $scope.config.HostConfig.ExtraHosts) {
|
||||||
|
if ({}.hasOwnProperty.call($scope.config.HostConfig.ExtraHosts, h)) {
|
||||||
|
$scope.formValues.ExtraHosts.push({'value': $scope.config.HostConfig.ExtraHosts[h]});
|
||||||
|
$scope.config.HostConfig.ExtraHosts = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerEnvrionmentVariables(d) {
|
||||||
|
var envArr = [];
|
||||||
|
for (var e in $scope.config.Env) {
|
||||||
|
if ({}.hasOwnProperty.call($scope.config.Env, e)) {
|
||||||
|
var arr = $scope.config.Env[e].split(/\=(.+)/);
|
||||||
|
envArr.push({'name': arr[0], 'value': arr[1]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.config.Env = envArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerLabels(d) {
|
||||||
|
for (var l in $scope.config.Labels) {
|
||||||
|
if ({}.hasOwnProperty.call($scope.config.Labels, l)) {
|
||||||
|
$scope.formValues.Labels.push({ name: l, value: $scope.config.Labels[l]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerConsole(d) {
|
||||||
|
if ($scope.config.OpenStdin && $scope.config.Tty) {
|
||||||
|
$scope.formValues.Console = 'both';
|
||||||
|
} else if (!$scope.config.OpenStdin && $scope.config.Tty) {
|
||||||
|
$scope.formValues.Console = 'tty';
|
||||||
|
} else if ($scope.config.OpenStdin && !$scope.config.Tty) {
|
||||||
|
$scope.formValues.Console = 'interactive';
|
||||||
|
} else if (!$scope.config.OpenStdin && !$scope.config.Tty) {
|
||||||
|
$scope.formValues.Console = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerDevices(d) {
|
||||||
|
var path = [];
|
||||||
|
for (var dev in $scope.config.HostConfig.Devices) {
|
||||||
|
if ({}.hasOwnProperty.call($scope.config.HostConfig.Devices, dev)) {
|
||||||
|
var device = $scope.config.HostConfig.Devices[dev];
|
||||||
|
path.push({'pathOnHost': device.PathOnHost, 'pathInContainer': device.PathInContainer});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.config.HostConfig.Devices = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerImageConfig(d) {
|
||||||
|
// If no registry found, we let default DockerHub and let full image path
|
||||||
|
var imageInfo = ImageHelper.extractImageAndRegistryFromRepository($scope.config.Image);
|
||||||
|
RegistryService.retrieveRegistryFromRepository($scope.config.Image)
|
||||||
|
.then(function success(data) {
|
||||||
|
if (data) {
|
||||||
|
$scope.config.Image = imageInfo.image;
|
||||||
|
$scope.formValues.Registry = data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to retrive registry');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromContainerSpec() {
|
||||||
|
// Get container
|
||||||
|
Container.get({ id: $stateParams.from }).$promise
|
||||||
|
.then(function success(d) {
|
||||||
|
var fromContainer = new ContainerDetailsViewModel(d);
|
||||||
|
if (!fromContainer.ResourceControl) {
|
||||||
|
$scope.formValues.AccessControlData.AccessControlEnabled = false;
|
||||||
|
}
|
||||||
|
$scope.fromContainer = fromContainer;
|
||||||
|
$scope.config = ContainerHelper.configFromContainer(fromContainer.Model);
|
||||||
|
loadFromContainerCmd(d);
|
||||||
|
loadFromContainerPortBindings(d);
|
||||||
|
loadFromContainerVolumes(d);
|
||||||
|
loadFromContainerNetworkConfig(d);
|
||||||
|
loadFromContainerEnvrionmentVariables(d);
|
||||||
|
loadFromContainerLabels(d);
|
||||||
|
loadFromContainerConsole(d);
|
||||||
|
loadFromContainerDevices(d);
|
||||||
|
loadFromContainerImageConfig(d);
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to retrieve container');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
Volume.query({}, function (d) {
|
Volume.query({}, function (d) {
|
||||||
$scope.availableVolumes = d.Volumes;
|
$scope.availableVolumes = d.Volumes;
|
||||||
|
@ -264,6 +473,12 @@ function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper,
|
||||||
Container.query({}, function (d) {
|
Container.query({}, function (d) {
|
||||||
var containers = d;
|
var containers = d;
|
||||||
$scope.runningContainers = containers;
|
$scope.runningContainers = containers;
|
||||||
|
if ($stateParams.from !== '') {
|
||||||
|
loadFromContainerSpec();
|
||||||
|
} else {
|
||||||
|
$scope.fromContainer = {};
|
||||||
|
$scope.formValues.Registry = {};
|
||||||
|
}
|
||||||
}, function(e) {
|
}, function(e) {
|
||||||
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
||||||
});
|
});
|
||||||
|
@ -283,19 +498,27 @@ function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper,
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.create = function () {
|
$scope.create = function () {
|
||||||
$('#createContainerSpinner').show();
|
confirmCreateContainer()
|
||||||
|
.then(function success(confirm) {
|
||||||
|
if (!confirm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$('#createContainerSpinner').show();
|
||||||
|
var accessControlData = $scope.formValues.AccessControlData;
|
||||||
|
var userDetails = Authentication.getUserDetails();
|
||||||
|
var isAdmin = userDetails.role === 1 ? true : false;
|
||||||
|
|
||||||
var accessControlData = $scope.formValues.AccessControlData;
|
if (!validateForm(accessControlData, isAdmin)) {
|
||||||
var userDetails = Authentication.getUserDetails();
|
$('#createContainerSpinner').hide();
|
||||||
var isAdmin = userDetails.role === 1 ? true : false;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateForm(accessControlData, isAdmin)) {
|
var config = prepareConfiguration();
|
||||||
$('#createContainerSpinner').hide();
|
createContainer(config, accessControlData);
|
||||||
return;
|
})
|
||||||
}
|
.catch(function error(err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to create container');
|
||||||
var config = prepareConfiguration();
|
});
|
||||||
createContainer(config, accessControlData);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function createContainer(config, accessControlData) {
|
function createContainer(config, accessControlData) {
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- image-and-registry -->
|
<!-- image-and-registry -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<por-image-registry image="config.Image" registry="formValues.Registry"></por-image-registry>
|
<por-image-registry image="config.Image" registry="formValues.Registry" ng-if="formValues.Registry"></por-image-registry>
|
||||||
</div>
|
</div>
|
||||||
<!-- !image-and-registry -->
|
<!-- !image-and-registry -->
|
||||||
<!-- always-pull -->
|
<!-- always-pull -->
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !port-mapping -->
|
<!-- !port-mapping -->
|
||||||
<!-- access-control -->
|
<!-- access-control -->
|
||||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
<por-access-control-form form-data="formValues.AccessControlData" resource-control="fromContainer.ResourceControl" ng-if="applicationState.application.authentication && fromContainer"></por-access-control-form>
|
||||||
<!-- !access-control -->
|
<!-- !access-control -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
@ -110,6 +110,10 @@
|
||||||
<a type="button" class="btn btn-default btn-sm" ui-sref="containers">Cancel</a>
|
<a type="button" class="btn btn-default btn-sm" ui-sref="containers">Cancel</a>
|
||||||
<i id="createContainerSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
<i id="createContainerSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||||
|
<span ng-if="fromContainerMultipleNetworks" style="margin-left: 10px">
|
||||||
|
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||||
|
<span class="small text-muted" style="margin-left: 5px;">This container is connected to multiple networks, only one network will be kept at creation time.</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !actions -->
|
<!-- !actions -->
|
||||||
|
|
|
@ -12,7 +12,11 @@ function ($q, RegistryService, DockerHubService, Notifications) {
|
||||||
var dockerhub = data.dockerhub;
|
var dockerhub = data.dockerhub;
|
||||||
var registries = data.registries;
|
var registries = data.registries;
|
||||||
ctrl.availableRegistries = [dockerhub].concat(registries);
|
ctrl.availableRegistries = [dockerhub].concat(registries);
|
||||||
ctrl.registry = dockerhub;
|
if (!ctrl.registry.Id) {
|
||||||
|
ctrl.registry = dockerhub;
|
||||||
|
} else {
|
||||||
|
ctrl.registry = _.find(ctrl.availableRegistries, { 'Id': ctrl.registry.Id });
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||||
|
|
|
@ -7,5 +7,56 @@ angular.module('portainer.helpers')
|
||||||
return splitargs(command);
|
return splitargs(command);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
helper.commandArrayToString = function(array) {
|
||||||
|
return array.map(function(elem) {
|
||||||
|
return '\'' + elem + '\'';
|
||||||
|
}).join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.configFromContainer = function(container) {
|
||||||
|
var config = container.Config;
|
||||||
|
// HostConfig
|
||||||
|
config.HostConfig = container.HostConfig;
|
||||||
|
// Name
|
||||||
|
config.name = container.Name.replace(/^\//g, '');
|
||||||
|
// Network
|
||||||
|
var mode = config.HostConfig.NetworkMode;
|
||||||
|
config.NetworkingConfig = {
|
||||||
|
'EndpointsConfig': {}
|
||||||
|
};
|
||||||
|
config.NetworkingConfig.EndpointsConfig = container.NetworkSettings.Networks;
|
||||||
|
if (mode.indexOf('container:') !== -1) {
|
||||||
|
delete config.Hostname;
|
||||||
|
delete config.ExposedPorts;
|
||||||
|
}
|
||||||
|
// Set volumes
|
||||||
|
var binds = [];
|
||||||
|
var volumes = {};
|
||||||
|
for (var v in container.Mounts) {
|
||||||
|
if ({}.hasOwnProperty.call(container.Mounts, v)) {
|
||||||
|
var mount = container.Mounts[v];
|
||||||
|
var volume = {
|
||||||
|
'type': mount.Type,
|
||||||
|
'name': mount.Name || mount.Source,
|
||||||
|
'containerPath': mount.Destination,
|
||||||
|
'readOnly': mount.RW === false
|
||||||
|
};
|
||||||
|
var name = mount.Name || mount.Source;
|
||||||
|
var containerPath = mount.Destination;
|
||||||
|
if (name && containerPath) {
|
||||||
|
var bind = name + ':' + containerPath;
|
||||||
|
volumes[containerPath] = {};
|
||||||
|
if (mount.RW === false) {
|
||||||
|
bind += ':ro';
|
||||||
|
}
|
||||||
|
binds.push(bind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.HostConfig.Binds = binds;
|
||||||
|
config.Volumes = volumes;
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
return helper;
|
return helper;
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
function ContainerDetailsViewModel(data) {
|
function ContainerDetailsViewModel(data) {
|
||||||
|
this.Model = data;
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
this.State = data.State;
|
this.State = data.State;
|
||||||
this.Created = data.Created;
|
this.Created = data.Created;
|
||||||
|
|
|
@ -108,5 +108,19 @@ angular.module('portainer.services')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
service.confirmExperimentalFeature = function(callback) {
|
||||||
|
service.confirm({
|
||||||
|
title: 'Experimental feature',
|
||||||
|
message: 'This feature is currently experimental, please use with caution.',
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: 'Continue',
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: callback
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
}]);
|
}]);
|
||||||
|
|
Loading…
Reference in New Issue