mirror of https://github.com/portainer/portainer
wip containers es9 refactor
parent
b3bf6b6115
commit
fdb44a0cc8
|
@ -2,6 +2,7 @@ env:
|
|||
browser: true
|
||||
jquery: true
|
||||
node: true
|
||||
es6: true
|
||||
|
||||
globals:
|
||||
angular: true
|
||||
|
@ -20,7 +21,7 @@ rules:
|
|||
# # Possible Errors
|
||||
# no-await-in-loop: off
|
||||
# no-cond-assign: error
|
||||
# no-console: off
|
||||
no-console: off
|
||||
# no-constant-condition: error
|
||||
# no-control-regex: error
|
||||
# no-debugger: error
|
||||
|
|
|
@ -25,7 +25,7 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
'content@': {
|
||||
templateUrl: './views/configs/configs.html',
|
||||
controller: 'ConfigsController',
|
||||
controllerAs: 'ctrl'
|
||||
controllerAs: 'ctrl',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
'content@': {
|
||||
templateUrl: './views/configs/edit/config.html',
|
||||
controller: 'ConfigController',
|
||||
controllerAs: 'ctrl'
|
||||
controllerAs: 'ctrl',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -49,7 +49,7 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
'content@': {
|
||||
templateUrl: './views/configs/create/createconfig.html',
|
||||
controller: 'CreateConfigController',
|
||||
controllerAs: 'ctrl'
|
||||
controllerAs: 'ctrl',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -60,7 +60,8 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/containers/containers.html',
|
||||
controller: 'ContainersController'
|
||||
controller: 'ContainersController',
|
||||
controllerAs: 'ctrl',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -92,8 +93,9 @@ angular.module('portainer.docker', ['portainer.app'])
|
|||
url: '/new?nodeName&from',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/containers/create/createcontainer.html',
|
||||
controller: 'CreateContainerController'
|
||||
templateUrl: './views/containers/create/createContainer.html',
|
||||
controller: 'CreateContainerController',
|
||||
controllerAs: 'ctrl',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
</rd-header-title>
|
||||
<rd-header-content>Containers</rd-header-content>
|
||||
</rd-header>
|
||||
<information-panel-offline ng-if="offlineMode"></information-panel-offline>
|
||||
<information-panel-offline ng-if="ctrl.offlineMode"></information-panel-offline>
|
||||
<div class="row">
|
||||
<div class="col-sm-12" ng-if="containers">
|
||||
<div class="col-sm-12" ng-if="ctrl.containers">
|
||||
<containers-datatable
|
||||
title-text="Containers" title-icon="fa-server"
|
||||
dataset="containers" table-key="containers"
|
||||
dataset="ctrl.containers" table-key="containers"
|
||||
order-by="Status"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
show-host-column="applicationState.endpoint.mode.agentProxy && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'"
|
||||
show-add-action="true"
|
||||
offline-mode="offlineMode"
|
||||
offline-mode="ctrl.offlineMode"
|
||||
></containers-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('ContainersController', ['$scope', 'ContainerService', 'Notifications', 'EndpointProvider',
|
||||
function ($scope, ContainerService, Notifications, EndpointProvider) {
|
||||
import angular from 'angular';
|
||||
|
||||
$scope.offlineMode = false;
|
||||
|
||||
function initView() {
|
||||
ContainerService.containers(1)
|
||||
.then(function success(data) {
|
||||
$scope.containers = data;
|
||||
$scope.offlineMode = EndpointProvider.offlineMode();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve containers');
|
||||
$scope.containers = [];
|
||||
});
|
||||
class ContainersController {
|
||||
/* @ngInject */
|
||||
constructor(ContainerService, Notifications, EndpointProvider) {
|
||||
this.ContainerService = ContainerService;
|
||||
this.Notifications = Notifications;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.offlineMode = false;
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
async $onInit() {
|
||||
try {
|
||||
let data = await this.ContainerService.containers(1);
|
||||
this.containers = data;
|
||||
this.offlineMode = this.EndpointProvider.offlineMode();
|
||||
}
|
||||
catch(err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve containers');
|
||||
this.containers = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ContainersController;
|
||||
angular.module('portainer.docker').controller('ContainersController', ContainersController);
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
export function addVolume() {
|
||||
this.formValues.Volumes.push({ name: '', containerPath: '', readOnly: false, type: 'volume' });
|
||||
}
|
||||
|
||||
export function removeVolume(index) {
|
||||
this.formValues.Volumes.splice(index, 1);
|
||||
}
|
||||
|
||||
export function addEnvironmentVariable() {
|
||||
this.config.Env.push({ name: '', value: ''});
|
||||
}
|
||||
|
||||
export function removeEnvironmentVariable(index) {
|
||||
this.config.Env.splice(index, 1);
|
||||
}
|
||||
|
||||
export function addPortBinding() {
|
||||
this.config.HostConfig.PortBindings.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
||||
}
|
||||
|
||||
export function removePortBinding(index) {
|
||||
this.config.HostConfig.PortBindings.splice(index, 1);
|
||||
}
|
||||
|
||||
export function addLabel() {
|
||||
this.formValues.Labels.push({ name: '', value: ''});
|
||||
}
|
||||
|
||||
export function removeLabel(index) {
|
||||
this.formValues.Labels.splice(index, 1);
|
||||
}
|
||||
|
||||
export function addExtraHost() {
|
||||
this.formValues.ExtraHosts.push({ value: '' });
|
||||
}
|
||||
|
||||
export function removeExtraHost(index) {
|
||||
this.formValues.ExtraHosts.splice(index, 1);
|
||||
}
|
||||
|
||||
export function addDevice() {
|
||||
this.config.HostConfig.Devices.push({ pathOnHost: '', pathInContainer: '' });
|
||||
}
|
||||
|
||||
export function removeDevice(index) {
|
||||
this.config.HostConfig.Devices.splice(index, 1);
|
||||
}
|
||||
|
||||
export function addLogDriverOpt() {
|
||||
this.formValues.LogDriverOpts.push({ name: '', value: ''});
|
||||
}
|
||||
|
||||
export function removeLogDriverOpt(index) {
|
||||
this.formValues.LogDriverOpts.splice(index, 1);
|
||||
}
|
|
@ -0,0 +1,629 @@
|
|||
import _ from 'lodash-es';
|
||||
import { ContainerCapabilities, ContainerCapability } from 'Docker/models/containerCapabilities';
|
||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from 'Docker/models/container';
|
||||
import * as UIActions from './includes/createContainer.UIactions';
|
||||
import { create } from './includes/create';
|
||||
|
||||
|
||||
import angular from 'angular';
|
||||
|
||||
class CreateContainerController {
|
||||
/* @ngInject */
|
||||
constructor($q, $scope, $state, $timeout, $transition$, $filter, ContainerHelper, ImageHelper, VolumeService, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SystemService, SettingsService, PluginService, HttpRequestHelper) {
|
||||
this.$q = $q;
|
||||
this.$scope = $scope; // TODO : Remove this temporary scope save for ApplicationState
|
||||
this.$state = $state;
|
||||
this.$timeout = $timeout;
|
||||
this.$transition$ = $transition$;
|
||||
this.$filter = $filter;
|
||||
this.ContainerHelper = ContainerHelper;
|
||||
this.ImageHelper = ImageHelper;
|
||||
this.HttpRequestHelper = HttpRequestHelper;
|
||||
this.VolumeService = VolumeService;
|
||||
this.NetworkService = NetworkService;
|
||||
this.ContainerService = ContainerService;
|
||||
this.ImageService = ImageService;
|
||||
this.RegistryService = RegistryService;
|
||||
this.ResourceControlService = ResourceControlService;
|
||||
this.SystemService = SystemService;
|
||||
this.SettingsService = SettingsService;
|
||||
this.PluginService = PluginService;
|
||||
this.Authentication = Authentication;
|
||||
this.Notifications = Notifications;
|
||||
this.FormValidator = FormValidator;
|
||||
this.ModalService = ModalService;
|
||||
|
||||
this.formValues = {
|
||||
alwaysPull: true,
|
||||
Console: 'none',
|
||||
Volumes: [],
|
||||
NetworkContainer: '',
|
||||
Labels: [],
|
||||
ExtraHosts: [],
|
||||
MacAddress: '',
|
||||
IPv4: '',
|
||||
IPv6: '',
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
CpuLimit: 0,
|
||||
MemoryLimit: 0,
|
||||
MemoryReservation: 0,
|
||||
NodeName: null,
|
||||
capabilities: [],
|
||||
LogDriverName: '',
|
||||
LogDriverOpts: []
|
||||
};
|
||||
|
||||
this.extraNetworks = {};
|
||||
|
||||
this.state = {
|
||||
formValidationError: '',
|
||||
actionInProgress: false
|
||||
};
|
||||
|
||||
this.config = {
|
||||
Image: '',
|
||||
Env: [],
|
||||
Cmd: '',
|
||||
MacAddress: '',
|
||||
ExposedPorts: {},
|
||||
HostConfig: {
|
||||
RestartPolicy: {
|
||||
Name: 'no'
|
||||
},
|
||||
PortBindings: [],
|
||||
PublishAllPorts: false,
|
||||
Binds: [],
|
||||
AutoRemove: false,
|
||||
NetworkMode: 'bridge',
|
||||
Privileged: false,
|
||||
Runtime: '',
|
||||
ExtraHosts: [],
|
||||
Devices: [],
|
||||
CapAdd: [],
|
||||
CapDrop: []
|
||||
},
|
||||
NetworkingConfig: {
|
||||
EndpointsConfig: {}
|
||||
},
|
||||
Labels: {}
|
||||
};
|
||||
|
||||
this.fromContainerMultipleNetworks = false;
|
||||
// imports
|
||||
this.create = create.bind(this);
|
||||
|
||||
}
|
||||
|
||||
initUIActions() {
|
||||
// UI Actions
|
||||
for (const func in UIActions) {
|
||||
console.log(func);
|
||||
this[func] = UIActions[func];
|
||||
}
|
||||
// this.addVolume = UIActions.addVolume;
|
||||
}
|
||||
|
||||
refreshSlider() {
|
||||
this.$timeout(function () {
|
||||
this.$broadcast('rzSliderForceRender');
|
||||
});
|
||||
}
|
||||
|
||||
prepareImageConfig(config) {
|
||||
var image = config.Image;
|
||||
var registry = this.formValues.Registry;
|
||||
var imageConfig = this.ImageHelper.createImageConfigForContainer(image, registry.URL);
|
||||
config.Image = imageConfig.fromImage + ':' + imageConfig.tag;
|
||||
this.imageConfig = imageConfig;
|
||||
}
|
||||
|
||||
preparePortBindings(config) {
|
||||
var bindings = {};
|
||||
for (const portBinding of config.HostConfig.PortBindings) {
|
||||
if (portBinding.containerPort) {
|
||||
var key = portBinding.containerPort + '/' + portBinding.protocol;
|
||||
var binding = {};
|
||||
if (portBinding.hostPort && portBinding.hostPort.indexOf(':') > -1) {
|
||||
var hostAndPort = portBinding.hostPort.split(':');
|
||||
binding.HostIp = hostAndPort[0];
|
||||
binding.HostPort = hostAndPort[1];
|
||||
} else {
|
||||
binding.HostPort = portBinding.hostPort;
|
||||
}
|
||||
bindings[key] = [binding];
|
||||
config.ExposedPorts[key] = {};
|
||||
}
|
||||
}
|
||||
config.HostConfig.PortBindings = bindings;
|
||||
}
|
||||
|
||||
prepareConsole(config) {
|
||||
var value = this.formValues.Console;
|
||||
var openStdin = true;
|
||||
var tty = true;
|
||||
if (value === 'tty') {
|
||||
openStdin = false;
|
||||
} else if (value === 'interactive') {
|
||||
tty = false;
|
||||
} else if (value === 'none') {
|
||||
openStdin = false;
|
||||
tty = false;
|
||||
}
|
||||
config.OpenStdin = openStdin;
|
||||
config.Tty = tty;
|
||||
}
|
||||
|
||||
prepareEnvironmentVariables(config) {
|
||||
var env = [];
|
||||
for (const v of config.Env) {
|
||||
if (v.name && v.value) {
|
||||
env.push(v.name + '=' + v.value);
|
||||
}
|
||||
}
|
||||
config.Env = env;
|
||||
}
|
||||
|
||||
prepareVolumes(config) {
|
||||
var binds = [];
|
||||
var volumes = {};
|
||||
|
||||
for (const volume of this.formValues.Volumes) {
|
||||
var name = volume.name;
|
||||
var containerPath = volume.containerPath;
|
||||
if (name && containerPath) {
|
||||
var bind = name + ':' + containerPath;
|
||||
volumes[containerPath] = {};
|
||||
if (volume.readOnly) {
|
||||
bind += ':ro';
|
||||
}
|
||||
binds.push(bind);
|
||||
}
|
||||
}
|
||||
config.HostConfig.Binds = binds;
|
||||
config.Volumes = volumes;
|
||||
}
|
||||
|
||||
prepareNetworkConfig(config) {
|
||||
var mode = config.HostConfig.NetworkMode;
|
||||
var container = this.formValues.NetworkContainer;
|
||||
var containerName = container;
|
||||
if (container && typeof container === 'object') {
|
||||
containerName = this.$filter('trimcontainername')(container.Names[0]);
|
||||
}
|
||||
var networkMode = mode;
|
||||
if (containerName) {
|
||||
networkMode += ':' + containerName;
|
||||
config.Hostname = '';
|
||||
}
|
||||
config.HostConfig.NetworkMode = networkMode;
|
||||
config.MacAddress = this.formValues.MacAddress;
|
||||
|
||||
config.NetworkingConfig.EndpointsConfig[networkMode] = {
|
||||
IPAMConfig: {
|
||||
IPv4Address: this.formValues.IPv4,
|
||||
IPv6Address: this.formValues.IPv6
|
||||
}
|
||||
};
|
||||
|
||||
for (const v of this.formValues.ExtraHosts) {
|
||||
if (v.value) {
|
||||
config.HostConfig.ExtraHosts.push(v.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareLabels(config) {
|
||||
var labels = {};
|
||||
for (const label of this.formValues.Labels) {
|
||||
if (label.name && label.value) {
|
||||
labels[label.name] = label.value;
|
||||
}
|
||||
}
|
||||
config.Labels = labels;
|
||||
}
|
||||
|
||||
prepareDevices(config) {
|
||||
var path = [];
|
||||
for (const p of config.HostConfig.Devices) {
|
||||
if (p.pathOnHost) {
|
||||
if(p.pathInContainer === '') {
|
||||
p.pathInContainer = p.pathOnHost;
|
||||
}
|
||||
path.push({PathOnHost:p.pathOnHost,PathInContainer:p.pathInContainer,CgroupPermissions:'rwm'});
|
||||
}
|
||||
}
|
||||
config.HostConfig.Devices = path;
|
||||
}
|
||||
|
||||
prepareResources(config) {
|
||||
// Memory Limit - Round to 0.125
|
||||
var memoryLimit = (Math.round(this.formValues.MemoryLimit * 8) / 8).toFixed(3);
|
||||
memoryLimit *= 1024 * 1024;
|
||||
if (memoryLimit > 0) {
|
||||
config.HostConfig.Memory = memoryLimit;
|
||||
}
|
||||
// Memory Resevation - Round to 0.125
|
||||
var memoryReservation = (Math.round(this.formValues.MemoryReservation * 8) / 8).toFixed(3);
|
||||
memoryReservation *= 1024 * 1024;
|
||||
if (memoryReservation > 0) {
|
||||
config.HostConfig.MemoryReservation = memoryReservation;
|
||||
}
|
||||
// CPU Limit
|
||||
if (this.formValues.CpuLimit > 0) {
|
||||
config.HostConfig.NanoCpus = this.formValues.CpuLimit * 1000000000;
|
||||
}
|
||||
}
|
||||
|
||||
prepareLogDriver(config) {
|
||||
var logOpts = {};
|
||||
if (this.formValues.LogDriverName) {
|
||||
config.HostConfig.LogConfig = { Type: this.formValues.LogDriverName };
|
||||
if (this.formValues.LogDriverName !== 'none') {
|
||||
for (const opt of this.formValues.LogDriverOpts) {
|
||||
if (opt.name) {
|
||||
logOpts[opt.name] = opt.value;
|
||||
}
|
||||
}
|
||||
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
|
||||
config.HostConfig.LogConfig.Config = logOpts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareCapabilities(config) {
|
||||
console.log('prepare capabilities');
|
||||
var allowed = this.formValues.capabilities.filter(function(item) {return item.allowed === true;});
|
||||
var notAllowed = this.formValues.capabilities.filter(function(item) {return item.allowed === false;});
|
||||
|
||||
var getCapName = function(item) {return item.capability;};
|
||||
config.HostConfig.CapAdd = allowed.map(getCapName);
|
||||
config.HostConfig.CapDrop = notAllowed.map(getCapName);
|
||||
}
|
||||
|
||||
prepareConfiguration() {
|
||||
var config = angular.copy(this.config);
|
||||
config.Cmd = this.ContainerHelper.commandStringToArray(config.Cmd);
|
||||
this.prepareNetworkConfig(config);
|
||||
this.prepareImageConfig(config);
|
||||
this.preparePortBindings(config);
|
||||
this.prepareConsole(config);
|
||||
this.prepareEnvironmentVariables(config);
|
||||
this.prepareVolumes(config);
|
||||
this.prepareLabels(config);
|
||||
this.prepareDevices(config);
|
||||
this.prepareResources(config);
|
||||
this.prepareLogDriver(config);
|
||||
this.prepareCapabilities(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
loadFromContainerCmd() {
|
||||
if (this.config.Cmd) {
|
||||
this.config.Cmd = this.ContainerHelper.commandArrayToString(this.config.Cmd);
|
||||
} else {
|
||||
this.config.Cmd = '';
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerPortBindings() {
|
||||
var bindings = [];
|
||||
for (var p in this.config.HostConfig.PortBindings) {
|
||||
if ({}.hasOwnProperty.call(this.config.HostConfig.PortBindings, p)) {
|
||||
var hostPort = '';
|
||||
if (this.config.HostConfig.PortBindings[p][0].HostIp) {
|
||||
hostPort = this.config.HostConfig.PortBindings[p][0].HostIp + ':';
|
||||
}
|
||||
hostPort += this.config.HostConfig.PortBindings[p][0].HostPort;
|
||||
var b = {
|
||||
'hostPort': hostPort,
|
||||
'containerPort': p.split('/')[0],
|
||||
'protocol': p.split('/')[1]
|
||||
};
|
||||
bindings.push(b);
|
||||
}
|
||||
}
|
||||
this.config.HostConfig.PortBindings = bindings;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
this.formValues.Volumes.push(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resetNetworkConfig() {
|
||||
this.config.NetworkingConfig = {
|
||||
EndpointsConfig: {}
|
||||
};
|
||||
}
|
||||
|
||||
loadFromContainerNetworkConfig(d) {
|
||||
this.config.NetworkingConfig = {
|
||||
EndpointsConfig: {}
|
||||
};
|
||||
var networkMode = d.HostConfig.NetworkMode;
|
||||
if (networkMode === 'default') {
|
||||
this.config.HostConfig.NetworkMode = 'bridge';
|
||||
if (!_.find(this.availableNetworks, {'Name': 'bridge'})) {
|
||||
this.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
}
|
||||
if (this.config.HostConfig.NetworkMode.indexOf('container:') === 0) {
|
||||
var netContainer = this.config.HostConfig.NetworkMode.split(/^container:/)[1];
|
||||
this.config.HostConfig.NetworkMode = 'container';
|
||||
for (var c in this.runningContainers) {
|
||||
if (this.runningContainers[c].Names && this.runningContainers[c].Names[0] === '/' + netContainer) {
|
||||
this.formValues.NetworkContainer = this.runningContainers[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.fromContainerMultipleNetworks = Object.keys(d.NetworkSettings.Networks).length >= 2;
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode]) {
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig) {
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address) {
|
||||
this.formValues.IPv4 = d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address;
|
||||
}
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address) {
|
||||
this.formValues.IPv6 = d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.config.NetworkingConfig.EndpointsConfig[this.config.HostConfig.NetworkMode] = d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode];
|
||||
// Mac Address
|
||||
if(Object.keys(d.NetworkSettings.Networks).length) {
|
||||
var firstNetwork = d.NetworkSettings.Networks[Object.keys(d.NetworkSettings.Networks)[0]];
|
||||
this.formValues.MacAddress = firstNetwork.MacAddress;
|
||||
this.config.NetworkingConfig.EndpointsConfig[this.config.HostConfig.NetworkMode] = firstNetwork;
|
||||
this.extraNetworks = angular.copy(d.NetworkSettings.Networks);
|
||||
delete this.extraNetworks[Object.keys(d.NetworkSettings.Networks)[0]];
|
||||
} else {
|
||||
this.formValues.MacAddress = '';
|
||||
}
|
||||
|
||||
// ExtraHosts
|
||||
if (this.config.HostConfig.ExtraHosts) {
|
||||
var extraHosts = this.config.HostConfig.ExtraHosts;
|
||||
for (var i = 0; i < extraHosts.length; i++) {
|
||||
var host = extraHosts[i];
|
||||
this.formValues.ExtraHosts.push({ 'value': host });
|
||||
}
|
||||
this.config.HostConfig.ExtraHosts = [];
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerEnvironmentVariables() {
|
||||
var envArr = [];
|
||||
for (var e in this.config.Env) {
|
||||
if ({}.hasOwnProperty.call(this.config.Env, e)) {
|
||||
var arr = this.config.Env[e].split(/\=(.+)/);
|
||||
envArr.push({'name': arr[0], 'value': arr[1]});
|
||||
}
|
||||
}
|
||||
this.config.Env = envArr;
|
||||
}
|
||||
|
||||
loadFromContainerLabels() {
|
||||
for (var l in this.config.Labels) {
|
||||
if ({}.hasOwnProperty.call(this.config.Labels, l)) {
|
||||
this.formValues.Labels.push({ name: l, value: this.config.Labels[l]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerConsole() {
|
||||
if (this.config.OpenStdin && this.config.Tty) {
|
||||
this.formValues.Console = 'both';
|
||||
} else if (!this.config.OpenStdin && this.config.Tty) {
|
||||
this.formValues.Console = 'tty';
|
||||
} else if (this.config.OpenStdin && !this.config.Tty) {
|
||||
this.formValues.Console = 'interactive';
|
||||
} else if (!this.config.OpenStdin && !this.config.Tty) {
|
||||
this.formValues.Console = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerDevices() {
|
||||
var path = [];
|
||||
for (var dev in this.config.HostConfig.Devices) {
|
||||
if ({}.hasOwnProperty.call(this.config.HostConfig.Devices, dev)) {
|
||||
var device = this.config.HostConfig.Devices[dev];
|
||||
path.push({'pathOnHost': device.PathOnHost, 'pathInContainer': device.PathInContainer});
|
||||
}
|
||||
}
|
||||
this.config.HostConfig.Devices = path;
|
||||
}
|
||||
|
||||
loadFromContainerImageConfig() {
|
||||
var imageInfo = this.ImageHelper.extractImageAndRegistryFromRepository(this.config.Image);
|
||||
this.RegistryService.retrieveRegistryFromRepository(this.config.Image)
|
||||
.then(function success(data) {
|
||||
if (data) {
|
||||
this.config.Image = imageInfo.image;
|
||||
this.formValues.Registry = data;
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrive registry');
|
||||
});
|
||||
}
|
||||
|
||||
loadFromContainerResources(d) {
|
||||
if (d.HostConfig.NanoCpus) {
|
||||
this.formValues.CpuLimit = d.HostConfig.NanoCpus / 1000000000;
|
||||
}
|
||||
if (d.HostConfig.Memory) {
|
||||
this.formValues.MemoryLimit = d.HostConfig.Memory / 1024 / 1024;
|
||||
}
|
||||
if (d.HostConfig.MemoryReservation) {
|
||||
this.formValues.MemoryReservation = d.HostConfig.MemoryReservation / 1024 / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerCapabilities(d) {
|
||||
if (d.HostConfig.CapAdd) {
|
||||
for (const cap of d.HostConfig.CapAdd) {
|
||||
this.formValues.capabilities.push(new ContainerCapability(cap, true));
|
||||
}
|
||||
}
|
||||
if (d.HostConfig.CapDrop) {
|
||||
for (const cap of d.HostConfig.CapDrop) {
|
||||
this.formValues.capabilities.push(new ContainerCapability(cap, false));
|
||||
}
|
||||
}
|
||||
|
||||
var capabilities = new ContainerCapabilities();
|
||||
for (var i = 0; i < capabilities.length; i++) {
|
||||
var cap = capabilities[i];
|
||||
if (!_.find(this.formValues.capabilities, (item) => item.capability === cap.capability)) {
|
||||
this.formValues.capabilities.push(cap);
|
||||
}
|
||||
}
|
||||
|
||||
this.formValues.capabilities.sort((a, b) => a.capability < b.capability ? -1 : 1);
|
||||
}
|
||||
|
||||
loadFromContainerSpec() {
|
||||
// Get container
|
||||
this.ContainerService.container(this.$transition$.params().from).$promise
|
||||
.then(function success(d) {
|
||||
var fromContainer = new ContainerDetailsViewModel(d);
|
||||
if (fromContainer.ResourceControl && fromContainer.ResourceControl.Public) {
|
||||
this.formValues.AccessControlData.AccessControlEnabled = false;
|
||||
}
|
||||
this.fromContainer = fromContainer;
|
||||
this.config = this.ContainerHelper.configFromContainer(fromContainer.Model);
|
||||
this.loadFromContainerCmd(d);
|
||||
this.loadFromContainerLogging(d);
|
||||
this.loadFromContainerPortBindings(d);
|
||||
this.loadFromContainerVolumes(d);
|
||||
this.loadFromContainerNetworkConfig(d);
|
||||
this.loadFromContainerEnvironmentVariables(d);
|
||||
this.loadFromContainerLabels(d);
|
||||
this.loadFromContainerConsole(d);
|
||||
this.loadFromContainerDevices(d);
|
||||
this.loadFromContainerImageConfig(d);
|
||||
this.loadFromContainerResources(d);
|
||||
this.loadFromContainerCapabilities(d);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve container');
|
||||
});
|
||||
}
|
||||
|
||||
loadFromContainerLogging(config) {
|
||||
var logConfig = config.HostConfig.LogConfig;
|
||||
this.formValues.LogDriverName = logConfig.Type;
|
||||
this.formValues.LogDriverOpts = _.map(logConfig.Config, function (value, name) {
|
||||
return {
|
||||
name: name,
|
||||
value: value
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
var nodeName = this.$transition$.params().nodeName;
|
||||
this.formValues.NodeName = nodeName;
|
||||
this.HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||
try {
|
||||
let data = await this.VolumeService.volumes({});
|
||||
this.availableVolumes = data;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve volumes');
|
||||
}
|
||||
|
||||
var provider = this.$scope.applicationState.endpoint.mode.provider;
|
||||
var apiVersion = this.$scope.applicationState.endpoint.apiVersion;
|
||||
try {
|
||||
let data = await this.NetworkService.networks(
|
||||
provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE',
|
||||
false,
|
||||
provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
|
||||
);
|
||||
var networks = data;
|
||||
networks.push({ Name: 'container' });
|
||||
this.availableNetworks = networks;
|
||||
|
||||
if (_.find(networks, {'Name': 'nat'})) {
|
||||
this.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve networks');
|
||||
}
|
||||
|
||||
try {
|
||||
this.runningContainers = await this.ContainerService.containers();
|
||||
if (this.$transition$.params().from) {
|
||||
this.loadFromContainerSpec();
|
||||
} else {
|
||||
this.fromContainer = {};
|
||||
this.formValues.Registry = {};
|
||||
this.formValues.capabilities = new ContainerCapabilities();
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve running containers');
|
||||
}
|
||||
|
||||
try {
|
||||
let data = await this.SystemService.info();
|
||||
this.availableRuntimes = Object.keys(data.Runtimes);
|
||||
this.config.HostConfig.Runtime = '';
|
||||
this.state.sliderMaxCpu = 32;
|
||||
if (data.NCPU) {
|
||||
this.state.sliderMaxCpu = data.NCPU;
|
||||
}
|
||||
this.state.sliderMaxMemory = 32768;
|
||||
if (data.MemTotal) {
|
||||
this.state.sliderMaxMemory = Math.floor(data.MemTotal / 1000 / 1000);
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve engine details');
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
let data = await this.SettingsService.publicSettings();
|
||||
this.allowBindMounts = data.AllowBindMountsForRegularUsers;
|
||||
this.allowPrivilegedMode = data.AllowPrivilegedModeForRegularUsers;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
}
|
||||
|
||||
try {
|
||||
this.availableLoggingDrivers = await this.PluginService.loggingPlugins(apiVersion < 1.25);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load logging plugins');
|
||||
}
|
||||
|
||||
var userDetails = this.Authentication.getUserDetails();
|
||||
this.isAdmin = userDetails.role === 1;
|
||||
this.initUIActions();
|
||||
console.log(this);
|
||||
}
|
||||
|
||||
validateForm(accessControlData, isAdmin) {
|
||||
this.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = this.FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
this.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default CreateContainerController;
|
||||
angular.module('portainer.docker').controller('tmp', CreateContainerController);
|
|
@ -0,0 +1,437 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import { ContainerCapabilities, ContainerCapability } from 'Docker/models/containerCapabilities';
|
||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from 'Docker/models/container';
|
||||
import * as UIActions from './createContainer.UIactions';
|
||||
import createContainer from './createContainer.function';
|
||||
import prepareConfiguration from './prepareConfiguration.function';
|
||||
|
||||
class CreateContainerController {
|
||||
/* @ngInject */
|
||||
constructor($q, $scope, $state, $timeout, $transition$, $filter, ContainerHelper, ImageHelper, VolumeService, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SystemService, SettingsService, PluginService, HttpRequestHelper) {
|
||||
this.$q = $q;
|
||||
this.$scope = $scope; // TODO : Remove this temporary scope save for ApplicationState
|
||||
this.$state = $state;
|
||||
this.$timeout = $timeout;
|
||||
this.$transition$ = $transition$;
|
||||
this.$filter = $filter;
|
||||
this.ContainerHelper = ContainerHelper;
|
||||
this.ImageHelper = ImageHelper;
|
||||
this.HttpRequestHelper = HttpRequestHelper;
|
||||
this.VolumeService = VolumeService;
|
||||
this.NetworkService = NetworkService;
|
||||
this.ContainerService = ContainerService;
|
||||
this.ImageService = ImageService;
|
||||
this.RegistryService = RegistryService;
|
||||
this.ResourceControlService = ResourceControlService;
|
||||
this.SystemService = SystemService;
|
||||
this.SettingsService = SettingsService;
|
||||
this.PluginService = PluginService;
|
||||
this.Authentication = Authentication;
|
||||
this.Notifications = Notifications;
|
||||
this.FormValidator = FormValidator;
|
||||
this.ModalService = ModalService;
|
||||
|
||||
this.formValues = {
|
||||
alwaysPull: true,
|
||||
Console: 'none',
|
||||
Volumes: [],
|
||||
NetworkContainer: '',
|
||||
Labels: [],
|
||||
ExtraHosts: [],
|
||||
MacAddress: '',
|
||||
IPv4: '',
|
||||
IPv6: '',
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
CpuLimit: 0,
|
||||
MemoryLimit: 0,
|
||||
MemoryReservation: 0,
|
||||
NodeName: null,
|
||||
capabilities: [],
|
||||
LogDriverName: '',
|
||||
LogDriverOpts: []
|
||||
};
|
||||
|
||||
this.extraNetworks = {};
|
||||
|
||||
this.state = {
|
||||
formValidationError: '',
|
||||
actionInProgress: false
|
||||
};
|
||||
|
||||
this.config = {
|
||||
Image: '',
|
||||
Env: [],
|
||||
Cmd: '',
|
||||
MacAddress: '',
|
||||
ExposedPorts: {},
|
||||
HostConfig: {
|
||||
RestartPolicy: {
|
||||
Name: 'no'
|
||||
},
|
||||
PortBindings: [],
|
||||
PublishAllPorts: false,
|
||||
Binds: [],
|
||||
AutoRemove: false,
|
||||
NetworkMode: 'bridge',
|
||||
Privileged: false,
|
||||
Runtime: '',
|
||||
ExtraHosts: [],
|
||||
Devices: [],
|
||||
CapAdd: [],
|
||||
CapDrop: []
|
||||
},
|
||||
NetworkingConfig: {
|
||||
EndpointsConfig: {}
|
||||
},
|
||||
Labels: {}
|
||||
};
|
||||
|
||||
this.fromContainerMultipleNetworks = false;
|
||||
// bind partial-class functions to the controller
|
||||
this.createContainer = createContainer;
|
||||
this.prepareConfiguration = prepareConfiguration;
|
||||
}
|
||||
|
||||
refreshSlider() {
|
||||
this.$timeout(function () {
|
||||
this.$broadcast('rzSliderForceRender');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadFromContainerCmd() {
|
||||
if (this.config.Cmd) {
|
||||
this.config.Cmd = this.ContainerHelper.commandArrayToString(this.config.Cmd);
|
||||
} else {
|
||||
this.config.Cmd = '';
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerPortBindings() {
|
||||
var bindings = [];
|
||||
for (var p in this.config.HostConfig.PortBindings) {
|
||||
if ({}.hasOwnProperty.call(this.config.HostConfig.PortBindings, p)) {
|
||||
var hostPort = '';
|
||||
if (this.config.HostConfig.PortBindings[p][0].HostIp) {
|
||||
hostPort = this.config.HostConfig.PortBindings[p][0].HostIp + ':';
|
||||
}
|
||||
hostPort += this.config.HostConfig.PortBindings[p][0].HostPort;
|
||||
var b = {
|
||||
'hostPort': hostPort,
|
||||
'containerPort': p.split('/')[0],
|
||||
'protocol': p.split('/')[1]
|
||||
};
|
||||
bindings.push(b);
|
||||
}
|
||||
}
|
||||
this.config.HostConfig.PortBindings = bindings;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
this.formValues.Volumes.push(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resetNetworkConfig() {
|
||||
this.config.NetworkingConfig = {
|
||||
EndpointsConfig: {}
|
||||
};
|
||||
}
|
||||
|
||||
loadFromContainerNetworkConfig(d) {
|
||||
this.config.NetworkingConfig = {
|
||||
EndpointsConfig: {}
|
||||
};
|
||||
var networkMode = d.HostConfig.NetworkMode;
|
||||
if (networkMode === 'default') {
|
||||
this.config.HostConfig.NetworkMode = 'bridge';
|
||||
if (!_.find(this.availableNetworks, {'Name': 'bridge'})) {
|
||||
this.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
}
|
||||
if (this.config.HostConfig.NetworkMode.indexOf('container:') === 0) {
|
||||
var netContainer = this.config.HostConfig.NetworkMode.split(/^container:/)[1];
|
||||
this.config.HostConfig.NetworkMode = 'container';
|
||||
for (var c in this.runningContainers) {
|
||||
if (this.runningContainers[c].Names && this.runningContainers[c].Names[0] === '/' + netContainer) {
|
||||
this.formValues.NetworkContainer = this.runningContainers[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.fromContainerMultipleNetworks = Object.keys(d.NetworkSettings.Networks).length >= 2;
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode]) {
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig) {
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address) {
|
||||
this.formValues.IPv4 = d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv4Address;
|
||||
}
|
||||
if (d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address) {
|
||||
this.formValues.IPv6 = d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode].IPAMConfig.IPv6Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.config.NetworkingConfig.EndpointsConfig[this.config.HostConfig.NetworkMode] = d.NetworkSettings.Networks[this.config.HostConfig.NetworkMode];
|
||||
// Mac Address
|
||||
if(Object.keys(d.NetworkSettings.Networks).length) {
|
||||
var firstNetwork = d.NetworkSettings.Networks[Object.keys(d.NetworkSettings.Networks)[0]];
|
||||
this.formValues.MacAddress = firstNetwork.MacAddress;
|
||||
this.config.NetworkingConfig.EndpointsConfig[this.config.HostConfig.NetworkMode] = firstNetwork;
|
||||
this.extraNetworks = angular.copy(d.NetworkSettings.Networks);
|
||||
delete this.extraNetworks[Object.keys(d.NetworkSettings.Networks)[0]];
|
||||
} else {
|
||||
this.formValues.MacAddress = '';
|
||||
}
|
||||
|
||||
// ExtraHosts
|
||||
if (this.config.HostConfig.ExtraHosts) {
|
||||
var extraHosts = this.config.HostConfig.ExtraHosts;
|
||||
for (var i = 0; i < extraHosts.length; i++) {
|
||||
var host = extraHosts[i];
|
||||
this.formValues.ExtraHosts.push({ 'value': host });
|
||||
}
|
||||
this.config.HostConfig.ExtraHosts = [];
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerEnvironmentVariables() {
|
||||
var envArr = [];
|
||||
for (var e in this.config.Env) {
|
||||
if ({}.hasOwnProperty.call(this.config.Env, e)) {
|
||||
var arr = this.config.Env[e].split(/\=(.+)/);
|
||||
envArr.push({'name': arr[0], 'value': arr[1]});
|
||||
}
|
||||
}
|
||||
this.config.Env = envArr;
|
||||
}
|
||||
|
||||
loadFromContainerLabels() {
|
||||
for (var l in this.config.Labels) {
|
||||
if ({}.hasOwnProperty.call(this.config.Labels, l)) {
|
||||
this.formValues.Labels.push({ name: l, value: this.config.Labels[l]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerConsole() {
|
||||
if (this.config.OpenStdin && this.config.Tty) {
|
||||
this.formValues.Console = 'both';
|
||||
} else if (!this.config.OpenStdin && this.config.Tty) {
|
||||
this.formValues.Console = 'tty';
|
||||
} else if (this.config.OpenStdin && !this.config.Tty) {
|
||||
this.formValues.Console = 'interactive';
|
||||
} else if (!this.config.OpenStdin && !this.config.Tty) {
|
||||
this.formValues.Console = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerDevices() {
|
||||
var path = [];
|
||||
for (var dev in this.config.HostConfig.Devices) {
|
||||
if ({}.hasOwnProperty.call(this.config.HostConfig.Devices, dev)) {
|
||||
var device = this.config.HostConfig.Devices[dev];
|
||||
path.push({'pathOnHost': device.PathOnHost, 'pathInContainer': device.PathInContainer});
|
||||
}
|
||||
}
|
||||
this.config.HostConfig.Devices = path;
|
||||
}
|
||||
|
||||
loadFromContainerImageConfig() {
|
||||
var imageInfo = this.ImageHelper.extractImageAndRegistryFromRepository(this.config.Image);
|
||||
this.RegistryService.retrieveRegistryFromRepository(this.config.Image)
|
||||
.then(function success(data) {
|
||||
if (data) {
|
||||
this.config.Image = imageInfo.image;
|
||||
this.formValues.Registry = data;
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrive registry');
|
||||
});
|
||||
}
|
||||
|
||||
loadFromContainerResources(d) {
|
||||
if (d.HostConfig.NanoCpus) {
|
||||
this.formValues.CpuLimit = d.HostConfig.NanoCpus / 1000000000;
|
||||
}
|
||||
if (d.HostConfig.Memory) {
|
||||
this.formValues.MemoryLimit = d.HostConfig.Memory / 1024 / 1024;
|
||||
}
|
||||
if (d.HostConfig.MemoryReservation) {
|
||||
this.formValues.MemoryReservation = d.HostConfig.MemoryReservation / 1024 / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
loadFromContainerCapabilities(d) {
|
||||
if (d.HostConfig.CapAdd) {
|
||||
for (const cap of d.HostConfig.CapAdd) {
|
||||
this.formValues.capabilities.push(new ContainerCapability(cap, true));
|
||||
}
|
||||
}
|
||||
if (d.HostConfig.CapDrop) {
|
||||
for (const cap of d.HostConfig.CapDrop) {
|
||||
this.formValues.capabilities.push(new ContainerCapability(cap, false));
|
||||
}
|
||||
}
|
||||
|
||||
var capabilities = new ContainerCapabilities();
|
||||
for (var i = 0; i < capabilities.length; i++) {
|
||||
var cap = capabilities[i];
|
||||
if (!_.find(this.formValues.capabilities, (item) => item.capability === cap.capability)) {
|
||||
this.formValues.capabilities.push(cap);
|
||||
}
|
||||
}
|
||||
|
||||
this.formValues.capabilities.sort((a, b) => a.capability < b.capability ? -1 : 1);
|
||||
}
|
||||
|
||||
loadFromContainerSpec() {
|
||||
// Get container
|
||||
this.Container.get({ id: this.$transition$.params().from }).$promise
|
||||
.then(function success(d) {
|
||||
var fromContainer = new ContainerDetailsViewModel(d);
|
||||
if (fromContainer.ResourceControl && fromContainer.ResourceControl.Public) {
|
||||
this.formValues.AccessControlData.AccessControlEnabled = false;
|
||||
}
|
||||
this.fromContainer = fromContainer;
|
||||
this.config = this.ContainerHelper.configFromContainer(fromContainer.Model);
|
||||
this.loadFromContainerCmd(d);
|
||||
this.loadFromContainerLogging(d);
|
||||
this.loadFromContainerPortBindings(d);
|
||||
this.loadFromContainerVolumes(d);
|
||||
this.loadFromContainerNetworkConfig(d);
|
||||
this.loadFromContainerEnvironmentVariables(d);
|
||||
this.loadFromContainerLabels(d);
|
||||
this.loadFromContainerConsole(d);
|
||||
this.loadFromContainerDevices(d);
|
||||
this.loadFromContainerImageConfig(d);
|
||||
this.loadFromContainerResources(d);
|
||||
this.loadFromContainerCapabilities(d);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve container');
|
||||
});
|
||||
}
|
||||
|
||||
loadFromContainerLogging(config) {
|
||||
var logConfig = config.HostConfig.LogConfig;
|
||||
this.formValues.LogDriverName = logConfig.Type;
|
||||
this.formValues.LogDriverOpts = _.map(logConfig.Config, function (value, name) {
|
||||
return {
|
||||
name: name,
|
||||
value: value
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
initUIActions() {
|
||||
for (const func in UIActions) {
|
||||
this[func] = UIActions[func];
|
||||
}
|
||||
}
|
||||
|
||||
async $onInit() {
|
||||
this.initUIActions();
|
||||
var nodeName = this.$transition$.params().nodeName;
|
||||
this.formValues.NodeName = nodeName;
|
||||
this.HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||
try {
|
||||
let data = await this.VolumeService.volumes({});
|
||||
this.availableVolumes = data.Volumes;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve volumes');
|
||||
}
|
||||
|
||||
var provider = this.$scope.applicationState.endpoint.mode.provider;
|
||||
var apiVersion = this.$scope.applicationState.endpoint.apiVersion;
|
||||
try {
|
||||
let data = await this.NetworkService.networks(
|
||||
provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE',
|
||||
false,
|
||||
provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
|
||||
);
|
||||
var networks = data;
|
||||
networks.push({ Name: 'container' });
|
||||
this.availableNetworks = networks;
|
||||
|
||||
if (_.find(networks, {'Name': 'nat'})) {
|
||||
this.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve networks');
|
||||
}
|
||||
|
||||
try {
|
||||
this.runningContainers = await this.ContainerService.containers();
|
||||
if (this.$transition$.params().from) {
|
||||
this.loadFromContainerSpec();
|
||||
} else {
|
||||
this.fromContainer = {};
|
||||
this.formValues.Registry = {};
|
||||
this.formValues.capabilities = new ContainerCapabilities();
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve running containers');
|
||||
}
|
||||
|
||||
try {
|
||||
let data = await this.SystemService.info();
|
||||
this.availableRuntimes = Object.keys(data.Runtimes);
|
||||
this.config.HostConfig.Runtime = '';
|
||||
this.state.sliderMaxCpu = 32;
|
||||
if (data.NCPU) {
|
||||
this.state.sliderMaxCpu = data.NCPU;
|
||||
}
|
||||
this.state.sliderMaxMemory = 32768;
|
||||
if (data.MemTotal) {
|
||||
this.state.sliderMaxMemory = Math.floor(data.MemTotal / 1000 / 1000);
|
||||
}
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve engine details');
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
let data = await this.SettingsService.publicSettings();
|
||||
this.allowBindMounts = data.AllowBindMountsForRegularUsers;
|
||||
this.allowPrivilegedMode = data.AllowPrivilegedModeForRegularUsers;
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
}
|
||||
|
||||
try {
|
||||
this.availableLoggingDrivers = await this.PluginService.loggingPlugins(apiVersion < 1.25);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to load logging plugins');
|
||||
}
|
||||
|
||||
var userDetails = this.Authentication.getUserDetails();
|
||||
this.isAdmin = userDetails.role === 1;
|
||||
}
|
||||
|
||||
validateForm(accessControlData, isAdmin) {
|
||||
this.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = this.FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
this.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CreateContainerController;
|
||||
angular.module('portainer.docker').controller('CreateContainerController', CreateContainerController);
|
|
@ -0,0 +1,204 @@
|
|||
export default async function createContainer() {
|
||||
var oldContainer = null;
|
||||
|
||||
this.HttpRequestHelper.setPortainerAgentTargetHeader(this.formValues.NodeName);
|
||||
|
||||
let final = () => {
|
||||
this.state.actionInProgress = false;
|
||||
};
|
||||
|
||||
let setOldContainer = (container) => {
|
||||
oldContainer = container;
|
||||
return container;
|
||||
};
|
||||
|
||||
let findCurrentContainer = () => {
|
||||
let onQuerySuccess = (containers) => {
|
||||
if (!containers.length) {
|
||||
return;
|
||||
}
|
||||
return containers[0];
|
||||
};
|
||||
|
||||
let notifyOnError = (err) => {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve containers');
|
||||
}
|
||||
|
||||
return this.ContainerService.containers(1, { name: ['^/' + this.config.name + '$'] })
|
||||
.then(onQuerySuccess)
|
||||
.catch(notifyOnError);
|
||||
};
|
||||
|
||||
let startCreationProcess = (confirmed) => {
|
||||
if (!confirmed) {
|
||||
return this.$q.when();
|
||||
}
|
||||
if (!validateAccessControl()) {
|
||||
return this.$q.when();
|
||||
}
|
||||
this.state.actionInProgress = true;
|
||||
return pullImageIfNeeded()
|
||||
.then(stopAndRenameContainer)
|
||||
.then(createNewContainer)
|
||||
.then(applyResourceControl)
|
||||
.then(connectToExtraNetworks)
|
||||
.then(removeOldContainer)
|
||||
.then(onSuccess)
|
||||
.catch(onCreationProcessFail);
|
||||
};
|
||||
|
||||
let onCreationProcessFail = (error) => {
|
||||
var deferred = this.$q.defer();
|
||||
removeNewContainer()
|
||||
.then(restoreOldContainerName)
|
||||
.then(() => deferred.reject(error))
|
||||
.catch((restoreError) => deferred.reject(restoreError));
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
let removeNewContainer = () => {
|
||||
let onContainerLoaded = (container) => {
|
||||
if (container && (!oldContainer || container.Id !== oldContainer.Id)) {
|
||||
return this.ContainerService.remove(container, true);
|
||||
}
|
||||
};
|
||||
|
||||
return findCurrentContainer().then(onContainerLoaded);
|
||||
};
|
||||
|
||||
let restoreOldContainerName = () => {
|
||||
if (!oldContainer) {
|
||||
return;
|
||||
}
|
||||
return this.ContainerService.renameContainer(oldContainer.Id, oldContainer.Names[0].substring(1));
|
||||
};
|
||||
|
||||
let confirmCreateContainer = (container) => {
|
||||
if (!container) {
|
||||
return this.$q.when(true);
|
||||
}
|
||||
let showConfirmationModal = () => {
|
||||
var deferred = this.$q.defer();
|
||||
|
||||
let onConfirm = (confirmed) => deferred.resolve(confirmed);
|
||||
|
||||
this.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: onConfirm
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
return showConfirmationModal();
|
||||
};
|
||||
|
||||
let stopAndRenameContainer = () => {
|
||||
if (!oldContainer) {
|
||||
return this.$q.when();
|
||||
}
|
||||
return stopContainerIfNeeded(oldContainer)
|
||||
.then(renameContainer);
|
||||
};
|
||||
|
||||
let stopContainerIfNeeded = (oldContainer) => {
|
||||
if (oldContainer.State !== 'running') {
|
||||
return this.$q.when();
|
||||
}
|
||||
return this.ContainerService.stopContainer(oldContainer.Id);
|
||||
};
|
||||
|
||||
let renameContainer = () => {
|
||||
return this.ContainerService.renameContainer(oldContainer.Id, oldContainer.Names[0].substring(1) + '-old');
|
||||
};
|
||||
|
||||
let pullImageIfNeeded = () => {
|
||||
return this.$q.when(this.formValues.alwaysPull &&
|
||||
this.ImageService.pullImage(this.config.Image, this.formValues.Registry, true));
|
||||
};
|
||||
|
||||
let createNewContainer = () => {
|
||||
var config = this.prepareConfiguration();
|
||||
return this.ContainerService.createAndStartContainer(config);
|
||||
};
|
||||
|
||||
let applyResourceControl = (newContainer) => {
|
||||
var containerIdentifier = newContainer.Id;
|
||||
var userId = this.Authentication.getUserDetails().ID;
|
||||
|
||||
let onApplyResourceControlSuccess = () => containerIdentifier;
|
||||
|
||||
return this.$q.when(this.ResourceControlService.applyResourceControl(
|
||||
'container',
|
||||
containerIdentifier,
|
||||
userId,
|
||||
this.formValues.AccessControlData, []
|
||||
)).then(onApplyResourceControlSuccess);
|
||||
};
|
||||
|
||||
let connectToExtraNetworks = (newContainerId) => {
|
||||
if (!this.extraNetworks) {
|
||||
return this.$q.when();
|
||||
}
|
||||
|
||||
var connectionPromises = Object.keys(this.extraNetworks).map((networkName) => {
|
||||
return this.NetworkService.connectContainer(networkName, newContainerId);
|
||||
});
|
||||
|
||||
return this.$q.all(connectionPromises);
|
||||
};
|
||||
|
||||
let removeOldContainer = () => {
|
||||
var deferred = this.$q.defer();
|
||||
|
||||
if (!oldContainer) {
|
||||
deferred.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let notifyOnRemoval = () => {
|
||||
this.Notifications.success('Container Removed', oldContainer.Id);
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
let notifyOnRemoveError = (err) => {
|
||||
deferred.reject({ msg: 'Unable to remove container', err: err });
|
||||
};
|
||||
|
||||
this.ContainerService.remove(oldContainer, true)
|
||||
.then(notifyOnRemoval)
|
||||
.catch(notifyOnRemoveError);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
let notifyOnError = (err) => {
|
||||
this.Notifications.error('Failure', err, 'Unable to create container');
|
||||
};
|
||||
|
||||
let validateAccessControl = () => {
|
||||
var accessControlData = this.formValues.AccessControlData;
|
||||
var userDetails = this.Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
return this.validateForm(accessControlData, isAdmin);
|
||||
};
|
||||
|
||||
let onSuccess = () => {
|
||||
this.Notifications.success('Container successfully created');
|
||||
this.$state.go('docker.containers', {}, { reload: true });
|
||||
};
|
||||
|
||||
return findCurrentContainer()
|
||||
.then(setOldContainer)
|
||||
.then(confirmCreateContainer)
|
||||
.then(startCreationProcess)
|
||||
.catch(notifyOnError)
|
||||
.finally(final);
|
||||
}
|
|
@ -14,23 +14,23 @@
|
|||
<div class="form-group">
|
||||
<label for="container_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.name" id="container_name" placeholder="e.g. myContainer">
|
||||
<input type="text" class="form-control" ng-model="ctrl.config.name" id="container_name" placeholder="e.g. myContainer">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Image configuration
|
||||
</div>
|
||||
<div ng-if="!formValues.Registry && fromContainer">
|
||||
<div ng-if="!ctrl.formValues.Registry && fromContainer">
|
||||
<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>{{ ctrl.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 ng-if="formValues.Registry || !fromContainer">
|
||||
<div ng-if="ctrl.formValues.Registry || !fromContainer">
|
||||
<!-- image-and-registry -->
|
||||
<por-image-registry
|
||||
image="config.Image"
|
||||
registry="formValues.Registry"
|
||||
ng-if="formValues.Registry"
|
||||
image="ctrl.config.Image"
|
||||
registry="ctrl.formValues.Registry"
|
||||
ng-if="ctrl.formValues.Registry"
|
||||
auto-complete="true"
|
||||
label-class="col-sm-1" input-class="col-sm-11 col-md-5"
|
||||
></por-image-registry>
|
||||
|
@ -43,7 +43,7 @@
|
|||
<portainer-tooltip position="bottom" message="When enabled, Portainer will automatically try to pull the specified image before creating the container."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="formValues.alwaysPull"><i></i>
|
||||
<input type="checkbox" ng-model="ctrl.formValues.alwaysPull"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -60,7 +60,7 @@
|
|||
<portainer-tooltip position="bottom" message="When enabled, Portainer will let Docker automatically map a random port on the host to each one defined in the image Dockerfile."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="config.HostConfig.PublishAllPorts"><i></i>
|
||||
<input type="checkbox" ng-model="ctrl.config.HostConfig.PublishAllPorts"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,13 +69,13 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">Port mapping</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addPortBinding()">
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
|
||||
</span>
|
||||
</div>
|
||||
<!-- port-mapping-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="portBinding in config.HostConfig.PortBindings" style="margin-top: 2px;">
|
||||
<div ng-repeat="portBinding in ctrl.config.HostConfig.PortBindings" style="margin-top: 2px;">
|
||||
<!-- host-port -->
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
|
@ -97,7 +97,7 @@
|
|||
<label class="btn btn-primary" ng-model="portBinding.protocol" uib-btn-radio="'tcp'">TCP</label>
|
||||
<label class="btn btn-primary" ng-model="portBinding.protocol" uib-btn-radio="'udp'">UDP</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removePortBinding($index)">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removePortBinding($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -113,12 +113,12 @@
|
|||
</div>
|
||||
<!-- node-selection -->
|
||||
<node-selector
|
||||
model="formValues.NodeName">
|
||||
model="ctrl.formValues.NodeName">
|
||||
</node-selector>
|
||||
<!-- !node-selection -->
|
||||
</div>
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData" resource-control="fromContainer.ResourceControl" ng-if="applicationState.application.authentication && fromContainer"></por-access-control-form>
|
||||
<por-access-control-form form-data="ctrl.formValues.AccessControlData" resource-control="ctrl.fromContainer.ResourceControl" ng-if="applicationState.application.authentication && fromContainer"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
|
@ -132,18 +132,18 @@
|
|||
<portainer-tooltip position="bottom" message="When enabled, Portainer will automatically remove the container when it exits. This is useful when you want to use the container only once."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="config.HostConfig.AutoRemove"><i></i>
|
||||
<input type="checkbox" ng-model="ctrl.config.HostConfig.AutoRemove"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !autoremove -->
|
||||
<div class="form-group">
|
||||
<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">
|
||||
<span ng-hide="state.actionInProgress">Deploy the container</span>
|
||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="ctrl.state.actionInProgress || !ctrl.config.Image || (!ctrl.formValues.Registry && fromContainer)" ng-click="ctrl.createContainer()" button-spinner="ctrl.state.actionInProgress">
|
||||
<span ng-hide="ctrl.state.actionInProgress">Deploy the container</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Deployment in progress...</span>
|
||||
</button>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
<span class="text-danger" ng-if="ctrl.state.formValidationError" style="margin-left: 5px;">{{ ctrl.state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
@ -165,7 +165,7 @@
|
|||
<li class="interactive"><a data-target="#env" data-toggle="tab">Env</a></li>
|
||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||
<li class="interactive"><a data-target="#restart-policy" data-toggle="tab">Restart policy</a></li>
|
||||
<li class="interactive"><a data-target="#runtime-resources" ng-click="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
||||
<li class="interactive"><a data-target="#runtime-resources" ng-click="ctrl.refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
||||
<li class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li>
|
||||
</ul>
|
||||
<!-- tab-content -->
|
||||
|
@ -177,7 +177,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_command" class="col-sm-2 col-lg-1 control-label text-left">Command</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Cmd" id="container_command" placeholder="e.g. /usr/bin/nginx -t -c /mynginx.conf">
|
||||
<input type="text" class="form-control" ng-model="ctrl.config.Cmd" id="container_command" placeholder="e.g. /usr/bin/nginx -t -c /mynginx.conf">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !command-input -->
|
||||
|
@ -185,7 +185,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_entrypoint" class="col-sm-2 col-lg-1 control-label text-left">Entry Point</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Entrypoint" id="container_entrypoint" placeholder="e.g. /bin/sh -c">
|
||||
<input type="text" class="form-control" ng-model="ctrl.config.Entrypoint" id="container_entrypoint" placeholder="e.g. /bin/sh -c">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !entrypoint-input -->
|
||||
|
@ -193,11 +193,11 @@
|
|||
<div class="form-group">
|
||||
<label for="container_workingdir" class="col-sm-2 col-lg-1 control-label text-left">Working Dir</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-model="config.WorkingDir" id="container_workingdir" placeholder="e.g. /myapp">
|
||||
<input type="text" class="form-control" ng-model="ctrl.config.WorkingDir" id="container_workingdir" placeholder="e.g. /myapp">
|
||||
</div>
|
||||
<label for="container_user" class="col-sm-1 control-label text-left">User</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-model="config.User" id="container_user" placeholder="e.g. nginx">
|
||||
<input type="text" class="form-control" ng-model="ctrl.config.User" id="container_user" placeholder="e.g. nginx">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !workdir-user-input -->
|
||||
|
@ -207,13 +207,13 @@
|
|||
<div class="col-sm-10 col-lg-11">
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="both">
|
||||
<input type="radio" name="container_console" ng-model="ctrl.formValues.Console" value="both">
|
||||
Interactive & TTY <span class="small text-muted">(-i -t)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="interactive">
|
||||
<input type="radio" name="container_console" ng-model="ctrl.formValues.Console" value="interactive">
|
||||
Interactive <span class="small text-muted">(-i)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -221,13 +221,13 @@
|
|||
<div class="col-sm-offset-2 col-sm-10 col-lg-offset-1 col-lg-11">
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="tty">
|
||||
<input type="radio" name="container_console" ng-model="ctrl.formValues.Console" value="tty">
|
||||
TTY <span class="small text-muted">(-t)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="none">
|
||||
<input type="radio" name="container_console" ng-model="ctrl.formValues.Console" value="none">
|
||||
None
|
||||
</label>
|
||||
</div>
|
||||
|
@ -242,7 +242,7 @@
|
|||
<div class="form-group">
|
||||
<label for="log-driver" class="col-sm-2 col-lg-1 control-label text-left">Driver</label>
|
||||
<div class="col-sm-4">
|
||||
<select class="form-control" ng-model="formValues.LogDriverName" id="log-driver">
|
||||
<select class="form-control" ng-model="ctrl.formValues.LogDriverName" id="log-driver">
|
||||
<option selected value="">Default logging driver</option>
|
||||
<option ng-repeat="driver in availableLoggingDrivers" ng-value="driver">{{ driver }}</option>
|
||||
<option value="none">none</option>
|
||||
|
@ -262,13 +262,13 @@
|
|||
Options
|
||||
<portainer-tooltip position="top" message="Add button is disabled unless a driver other than none or default is selected. Options are specific to the selected driver, refer to the driver documentation."></portainer-tooltip>
|
||||
</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="!formValues.LogDriverName || formValues.LogDriverName === 'none' || addLogDriverOpt(formValues.LogDriverName)">
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="!ctrl.formValues.LogDriverName || ctrl.formValues.LogDriverName === 'none' || ctrl.addLogDriverOpt(ctrl.formValues.LogDriverName)">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add logging driver option
|
||||
</span>
|
||||
</div>
|
||||
<!-- logging-opts-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="opt in formValues.LogDriverOpts" style="margin-top: 2px;">
|
||||
<div ng-repeat="opt in ctrl.formValues.LogDriverOpts" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">option</span>
|
||||
<input type="text" class="form-control" ng-model="opt.name" placeholder="e.g. FOO">
|
||||
|
@ -277,7 +277,7 @@
|
|||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="opt.value" placeholder="e.g. bar">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLogDriverOpt($index)">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeLogDriverOpt($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -297,13 +297,13 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Volume mapping</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addVolume()">
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addVolume()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional volume
|
||||
</span>
|
||||
</div>
|
||||
<!-- volumes-input-list -->
|
||||
<div class="form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="volume in formValues.Volumes">
|
||||
<div ng-repeat="volume in ctrl.formValues.Volumes">
|
||||
<!-- volume-line1 -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<!-- container-path -->
|
||||
|
@ -313,12 +313,12 @@
|
|||
</div>
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;" ng-if="isAdmin || allowBindMounts">
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;" ng-if="ctrl.isAdmin || ctrl.allowBindMounts">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.name = ''">Bind</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeVolume($index)">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeVolume($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -333,7 +333,7 @@
|
|||
<span class="input-group-addon">volume</span>
|
||||
<select class="form-control" ng-model="volume.name">
|
||||
<option selected disabled hidden value="">Select a volume</option>
|
||||
<option ng-repeat="vol in availableVolumes" ng-value="vol.Name">{{ vol.Name|truncate:30}} - {{ vol.Driver|truncate:30}}</option>
|
||||
<option ng-repeat="vol in ctrl.availableVolumes" ng-value="vol.Id">{{ vol.Id|truncate:30}} - {{ vol.Driver|truncate:30}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- !volume -->
|
||||
|
@ -373,7 +373,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_network" class="col-sm-2 col-lg-1 control-label text-left">Network</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" ng-model="config.HostConfig.NetworkMode" id="container_network" ng-change="resetNetworkConfig()">
|
||||
<select class="form-control" ng-model="ctrl.config.HostConfig.NetworkMode" id="container_network" ng-change="ctrl.resetNetworkConfig()">
|
||||
<option selected disabled hidden value="">Select a network</option>
|
||||
<option ng-repeat="net in availableNetworks | orderBy: 'Name'" ng-value="net.Name">{{ net.Name }}</option>
|
||||
</select>
|
||||
|
@ -381,10 +381,10 @@
|
|||
</div>
|
||||
<!-- !network-input -->
|
||||
<!-- container-name-input -->
|
||||
<div class="form-group" ng-if="config.HostConfig.NetworkMode == 'container'">
|
||||
<div class="form-group" ng-if="ctrl.config.HostConfig.NetworkMode == 'container'">
|
||||
<label for="container_network_container" class="col-sm-2 col-lg-1 control-label text-left">Container</label>
|
||||
<div class="col-sm-9">
|
||||
<select ng-options="container|containername for container in runningContainers" class="form-control" ng-model="formValues.NetworkContainer">
|
||||
<select ng-options="container|containername for container in runningContainers" class="form-control" ng-model="ctrl.formValues.NetworkContainer">
|
||||
<option selected disabled hidden value="">Select a container</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -394,7 +394,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_hostname" class="col-sm-2 col-lg-1 control-label text-left">Hostname</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Hostname" id="container_hostname" placeholder="e.g. web01">
|
||||
<input type="text" class="form-control" ng-model="ctrl.config.Hostname" id="container_hostname" placeholder="e.g. web01">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !hostname-input -->
|
||||
|
@ -402,7 +402,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_domainname" class="col-sm-2 col-lg-1 control-label text-left">Domain Name</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Domainname" id="container_domainname" placeholder="e.g. example.com">
|
||||
<input type="text" class="form-control" ng-model="ctrl.config.Domainname" id="container_domainname" placeholder="e.g. example.com">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !domainname -->
|
||||
|
@ -410,7 +410,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_macaddress" class="col-sm-2 col-lg-1 control-label text-left">Mac Address</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="formValues.MacAddress" id="container_macaddress" placeholder="e.g. 12-34-56-78-9a-bc">
|
||||
<input type="text" class="form-control" ng-model="ctrl.formValues.MacAddress" id="container_macaddress" placeholder="e.g. 12-34-56-78-9a-bc">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !mac-address-input -->
|
||||
|
@ -418,7 +418,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_ipv4" class="col-sm-2 col-lg-1 control-label text-left">IPv4 Address</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="formValues.IPv4" id="container_ipv4" placeholder="e.g. 172.20.0.7">
|
||||
<input type="text" class="form-control" ng-model="ctrl.formValues.IPv4" id="container_ipv4" placeholder="e.g. 172.20.0.7">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ipv4-input -->
|
||||
|
@ -426,7 +426,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_ipv6" class="col-sm-2 col-lg-1 control-label text-left">IPv6 Address</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="formValues.IPv6" id="container_ipv6" placeholder="e.g. a:b:c:d::1234">
|
||||
<input type="text" class="form-control" ng-model="ctrl.formValues.IPv6" id="container_ipv6" placeholder="e.g. a:b:c:d::1234">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ipv6-input -->
|
||||
|
@ -434,18 +434,18 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Hosts file entries</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addExtraHost()">
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addExtraHost()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add additional entry
|
||||
</span>
|
||||
</div>
|
||||
<!-- extra-hosts-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in formValues.ExtraHosts" style="margin-top: 2px;">
|
||||
<div ng-repeat="variable in ctrl.formValues.ExtraHosts" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. host:IP">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeExtraHost($index)">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeExtraHost($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -463,13 +463,13 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Labels</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addLabel()">
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addLabel()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add label
|
||||
</span>
|
||||
</div>
|
||||
<!-- labels-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="label in formValues.Labels" style="margin-top: 2px;">
|
||||
<div ng-repeat="label in ctrl.formValues.Labels" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo">
|
||||
|
@ -478,7 +478,7 @@
|
|||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeLabel($index)">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeLabel($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -496,13 +496,13 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Environment variables</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addEnvironmentVariable()">
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in config.Env" style="margin-top: 2px;">
|
||||
<div ng-repeat="variable in ctrl.config.Env" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="variable.name" placeholder="e.g. FOO">
|
||||
|
@ -511,7 +511,7 @@
|
|||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -531,16 +531,16 @@
|
|||
Restart policy
|
||||
</label>
|
||||
<div class="btn-group btn-group-sm" style="margin-left: 20px;">
|
||||
<label class="btn btn-primary" ng-model="config.HostConfig.RestartPolicy.Name" uib-btn-radio="'no'">
|
||||
<label class="btn btn-primary" ng-model="ctrl.config.HostConfig.RestartPolicy.Name" uib-btn-radio="'no'">
|
||||
Never
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="config.HostConfig.RestartPolicy.Name" uib-btn-radio="'always'">
|
||||
<label class="btn btn-primary" ng-model="ctrl.config.HostConfig.RestartPolicy.Name" uib-btn-radio="'always'">
|
||||
Always
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="config.HostConfig.RestartPolicy.Name" uib-btn-radio="'on-failure'">
|
||||
<label class="btn btn-primary" ng-model="ctrl.config.HostConfig.RestartPolicy.Name" uib-btn-radio="'on-failure'">
|
||||
On failure
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="config.HostConfig.RestartPolicy.Name" uib-btn-radio="'unless-stopped'">
|
||||
<label class="btn btn-primary" ng-model="ctrl.config.HostConfig.RestartPolicy.Name" uib-btn-radio="'unless-stopped'">
|
||||
Unless stopped
|
||||
</label>
|
||||
</div>
|
||||
|
@ -562,7 +562,7 @@
|
|||
Privileged mode
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="privileged_mode" ng-model="config.HostConfig.Privileged"><i></i>
|
||||
<input type="checkbox" name="privileged_mode" ng-model="ctrl.config.HostConfig.Privileged"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -571,7 +571,7 @@
|
|||
<div class="form-group">
|
||||
<label for="container_runtime" class="col-sm-1 control-label text-left">Runtime</label>
|
||||
<div class="col-sm-11">
|
||||
<select class="form-control" ng-model="config.HostConfig.Runtime"
|
||||
<select class="form-control" ng-model="ctrl.config.HostConfig.Runtime"
|
||||
id="container_runtime" ng-options="runtime for runtime in availableRuntimes">
|
||||
<option selected value="">Default</option>
|
||||
</select>
|
||||
|
@ -584,13 +584,13 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Devices</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addDevice()">
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addDevice()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add device
|
||||
</span>
|
||||
</div>
|
||||
<!-- devices-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="device in config.HostConfig.Devices" style="margin-top: 2px;">
|
||||
<div ng-repeat="device in ctrl.config.HostConfig.Devices" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="device.pathOnHost" placeholder="e.g. /dev/tty0">
|
||||
|
@ -599,7 +599,7 @@
|
|||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="device.pathInContainer" placeholder="e.g. /dev/tty0">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeDevice($index)">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="ctrl.removeDevice($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -616,10 +616,10 @@
|
|||
Memory reservation
|
||||
</label>
|
||||
<div class="col-sm-3">
|
||||
<slider model="formValues.MemoryReservation" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
|
||||
<slider model="ctrl.formValues.MemoryReservation" floor="0" ceil="ctrl.state.sliderMaxMemory" step="256" ng-if="ctrl.state.sliderMaxMemory"></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryReservation" id="memory-reservation">
|
||||
<input type="number" min="0" class="form-control" ng-model="ctrl.formValues.MemoryReservation" id="memory-reservation">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<p class="small text-muted" style="margin-top: 7px;">
|
||||
|
@ -634,10 +634,10 @@
|
|||
Memory limit
|
||||
</label>
|
||||
<div class="col-sm-3">
|
||||
<slider model="formValues.MemoryLimit" floor="0" ceil="state.sliderMaxMemory" step="256" ng-if="state.sliderMaxMemory"></slider>
|
||||
<slider model="ctrl.formValues.MemoryLimit" floor="0" ceil="ctrl.state.sliderMaxMemory" step="256" ng-if="ctrl.state.sliderMaxMemory"></slider>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="0" class="form-control" ng-model="formValues.MemoryLimit" id="memory-limit">
|
||||
<input type="number" min="0" class="form-control" ng-model="ctrl.formValues.MemoryLimit" id="memory-limit">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<p class="small text-muted" style="margin-top: 7px;">
|
||||
|
@ -652,7 +652,7 @@
|
|||
CPU limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<slider model="formValues.CpuLimit" floor="0" ceil="state.sliderMaxCpu" step="0.25" precision="2" ng-if="state.sliderMaxCpu"></slider>
|
||||
<slider model="ctrl.formValues.CpuLimit" floor="0" ceil="ctrl.state.sliderMaxCpu" step="0.25" precision="2" ng-if="ctrl.state.sliderMaxCpu"></slider>
|
||||
</div>
|
||||
<div class="col-sm-4" style="margin-top: 20px;">
|
||||
<p class="small text-muted">
|
||||
|
@ -666,7 +666,7 @@
|
|||
<!-- !tab-runtime-resources -->
|
||||
<!-- tab-container-capabilities -->
|
||||
<div class="tab-pane" id="container-capabilities">
|
||||
<container-capabilities capabilities="formValues.capabilities" ></container-capabilities>
|
||||
<container-capabilities capabilities="ctrl.formValues.capabilities"></container-capabilities>
|
||||
</div>
|
||||
<!-- !tab-container-capabilities -->
|
||||
</div>
|
|
@ -1,861 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||
|
||||
|
||||
angular.module('portainer.docker')
|
||||
.controller('CreateContainerController', ['$q', '$scope', '$state', '$timeout', '$transition$', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'NetworkService', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'FormValidator', 'ModalService', 'RegistryService', 'SystemService', 'SettingsService', 'PluginService', 'HttpRequestHelper',
|
||||
function ($q, $scope, $state, $timeout, $transition$, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, NetworkService, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, FormValidator, ModalService, RegistryService, SystemService, SettingsService, PluginService, HttpRequestHelper) {
|
||||
|
||||
$scope.create = create;
|
||||
|
||||
$scope.formValues = {
|
||||
alwaysPull: true,
|
||||
Console: 'none',
|
||||
Volumes: [],
|
||||
NetworkContainer: '',
|
||||
Labels: [],
|
||||
ExtraHosts: [],
|
||||
MacAddress: '',
|
||||
IPv4: '',
|
||||
IPv6: '',
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
CpuLimit: 0,
|
||||
MemoryLimit: 0,
|
||||
MemoryReservation: 0,
|
||||
NodeName: null,
|
||||
capabilities: [],
|
||||
LogDriverName: '',
|
||||
LogDriverOpts: []
|
||||
};
|
||||
|
||||
$scope.extraNetworks = {};
|
||||
|
||||
$scope.state = {
|
||||
formValidationError: '',
|
||||
actionInProgress: false
|
||||
};
|
||||
|
||||
$scope.refreshSlider = function () {
|
||||
$timeout(function () {
|
||||
$scope.$broadcast('rzSliderForceRender');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.config = {
|
||||
Image: '',
|
||||
Env: [],
|
||||
Cmd: '',
|
||||
MacAddress: '',
|
||||
ExposedPorts: {},
|
||||
HostConfig: {
|
||||
RestartPolicy: {
|
||||
Name: 'no'
|
||||
},
|
||||
PortBindings: [],
|
||||
PublishAllPorts: false,
|
||||
Binds: [],
|
||||
AutoRemove: false,
|
||||
NetworkMode: 'bridge',
|
||||
Privileged: false,
|
||||
Runtime: '',
|
||||
ExtraHosts: [],
|
||||
Devices: [],
|
||||
CapAdd: [],
|
||||
CapDrop: []
|
||||
},
|
||||
NetworkingConfig: {
|
||||
EndpointsConfig: {}
|
||||
},
|
||||
Labels: {}
|
||||
};
|
||||
|
||||
$scope.addVolume = function() {
|
||||
$scope.formValues.Volumes.push({ name: '', containerPath: '', readOnly: false, type: 'volume' });
|
||||
};
|
||||
|
||||
$scope.removeVolume = function(index) {
|
||||
$scope.formValues.Volumes.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function() {
|
||||
$scope.config.Env.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function(index) {
|
||||
$scope.config.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addPortBinding = function() {
|
||||
$scope.config.HostConfig.PortBindings.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
||||
};
|
||||
|
||||
$scope.removePortBinding = function(index) {
|
||||
$scope.config.HostConfig.PortBindings.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addLabel = function() {
|
||||
$scope.formValues.Labels.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeLabel = function(index) {
|
||||
$scope.formValues.Labels.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addExtraHost = function() {
|
||||
$scope.formValues.ExtraHosts.push({ value: '' });
|
||||
};
|
||||
|
||||
$scope.removeExtraHost = function(index) {
|
||||
$scope.formValues.ExtraHosts.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addDevice = function() {
|
||||
$scope.config.HostConfig.Devices.push({ pathOnHost: '', pathInContainer: '' });
|
||||
};
|
||||
|
||||
$scope.removeDevice = function(index) {
|
||||
$scope.config.HostConfig.Devices.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addLogDriverOpt = function() {
|
||||
$scope.formValues.LogDriverOpts.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeLogDriverOpt = function(index) {
|
||||
$scope.formValues.LogDriverOpts.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.fromContainerMultipleNetworks = false;
|
||||
|
||||
function prepareImageConfig(config) {
|
||||
var image = config.Image;
|
||||
var registry = $scope.formValues.Registry;
|
||||
var imageConfig = ImageHelper.createImageConfigForContainer(image, registry.URL);
|
||||
config.Image = imageConfig.fromImage + ':' + imageConfig.tag;
|
||||
$scope.imageConfig = imageConfig;
|
||||
}
|
||||
|
||||
function preparePortBindings(config) {
|
||||
var bindings = {};
|
||||
config.HostConfig.PortBindings.forEach(function (portBinding) {
|
||||
if (portBinding.containerPort) {
|
||||
var key = portBinding.containerPort + '/' + portBinding.protocol;
|
||||
var binding = {};
|
||||
if (portBinding.hostPort && portBinding.hostPort.indexOf(':') > -1) {
|
||||
var hostAndPort = portBinding.hostPort.split(':');
|
||||
binding.HostIp = hostAndPort[0];
|
||||
binding.HostPort = hostAndPort[1];
|
||||
} else {
|
||||
binding.HostPort = portBinding.hostPort;
|
||||
}
|
||||
bindings[key] = [binding];
|
||||
config.ExposedPorts[key] = {};
|
||||
}
|
||||
});
|
||||
config.HostConfig.PortBindings = bindings;
|
||||
}
|
||||
|
||||
function prepareConsole(config) {
|
||||
var value = $scope.formValues.Console;
|
||||
var openStdin = true;
|
||||
var tty = true;
|
||||
if (value === 'tty') {
|
||||
openStdin = false;
|
||||
} else if (value === 'interactive') {
|
||||
tty = false;
|
||||
} else if (value === 'none') {
|
||||
openStdin = false;
|
||||
tty = false;
|
||||
}
|
||||
config.OpenStdin = openStdin;
|
||||
config.Tty = tty;
|
||||
}
|
||||
|
||||
function prepareEnvironmentVariables(config) {
|
||||
var env = [];
|
||||
config.Env.forEach(function (v) {
|
||||
if (v.name && v.value) {
|
||||
env.push(v.name + '=' + v.value);
|
||||
}
|
||||
});
|
||||
config.Env = env;
|
||||
}
|
||||
|
||||
function prepareVolumes(config) {
|
||||
var binds = [];
|
||||
var volumes = {};
|
||||
|
||||
$scope.formValues.Volumes.forEach(function (volume) {
|
||||
var name = volume.name;
|
||||
var containerPath = volume.containerPath;
|
||||
if (name && containerPath) {
|
||||
var bind = name + ':' + containerPath;
|
||||
volumes[containerPath] = {};
|
||||
if (volume.readOnly) {
|
||||
bind += ':ro';
|
||||
}
|
||||
binds.push(bind);
|
||||
}
|
||||
});
|
||||
config.HostConfig.Binds = binds;
|
||||
config.Volumes = volumes;
|
||||
}
|
||||
|
||||
function prepareNetworkConfig(config) {
|
||||
var mode = config.HostConfig.NetworkMode;
|
||||
var container = $scope.formValues.NetworkContainer;
|
||||
var containerName = container;
|
||||
if (container && typeof container === 'object') {
|
||||
containerName = $filter('trimcontainername')(container.Names[0]);
|
||||
}
|
||||
var networkMode = mode;
|
||||
if (containerName) {
|
||||
networkMode += ':' + containerName;
|
||||
config.Hostname = '';
|
||||
}
|
||||
config.HostConfig.NetworkMode = networkMode;
|
||||
config.MacAddress = $scope.formValues.MacAddress;
|
||||
|
||||
config.NetworkingConfig.EndpointsConfig[networkMode] = {
|
||||
IPAMConfig: {
|
||||
IPv4Address: $scope.formValues.IPv4,
|
||||
IPv6Address: $scope.formValues.IPv6
|
||||
}
|
||||
};
|
||||
|
||||
$scope.formValues.ExtraHosts.forEach(function (v) {
|
||||
if (v.value) {
|
||||
config.HostConfig.ExtraHosts.push(v.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function prepareLabels(config) {
|
||||
var labels = {};
|
||||
$scope.formValues.Labels.forEach(function (label) {
|
||||
if (label.name && label.value) {
|
||||
labels[label.name] = label.value;
|
||||
}
|
||||
});
|
||||
config.Labels = labels;
|
||||
}
|
||||
|
||||
function prepareDevices(config) {
|
||||
var path = [];
|
||||
config.HostConfig.Devices.forEach(function (p) {
|
||||
if (p.pathOnHost) {
|
||||
if(p.pathInContainer === '') {
|
||||
p.pathInContainer = p.pathOnHost;
|
||||
}
|
||||
path.push({PathOnHost:p.pathOnHost,PathInContainer:p.pathInContainer,CgroupPermissions:'rwm'});
|
||||
}
|
||||
});
|
||||
config.HostConfig.Devices = path;
|
||||
}
|
||||
|
||||
function prepareResources(config) {
|
||||
// Memory Limit - Round to 0.125
|
||||
var memoryLimit = (Math.round($scope.formValues.MemoryLimit * 8) / 8).toFixed(3);
|
||||
memoryLimit *= 1024 * 1024;
|
||||
if (memoryLimit > 0) {
|
||||
config.HostConfig.Memory = memoryLimit;
|
||||
}
|
||||
// Memory Resevation - Round to 0.125
|
||||
var memoryReservation = (Math.round($scope.formValues.MemoryReservation * 8) / 8).toFixed(3);
|
||||
memoryReservation *= 1024 * 1024;
|
||||
if (memoryReservation > 0) {
|
||||
config.HostConfig.MemoryReservation = memoryReservation;
|
||||
}
|
||||
// CPU Limit
|
||||
if ($scope.formValues.CpuLimit > 0) {
|
||||
config.HostConfig.NanoCpus = $scope.formValues.CpuLimit * 1000000000;
|
||||
}
|
||||
}
|
||||
|
||||
function prepareLogDriver(config) {
|
||||
var logOpts = {};
|
||||
if ($scope.formValues.LogDriverName) {
|
||||
config.HostConfig.LogConfig = { Type: $scope.formValues.LogDriverName };
|
||||
if ($scope.formValues.LogDriverName !== 'none') {
|
||||
$scope.formValues.LogDriverOpts.forEach(function (opt) {
|
||||
if (opt.name) {
|
||||
logOpts[opt.name] = opt.value;
|
||||
}
|
||||
});
|
||||
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
|
||||
config.HostConfig.LogConfig.Config = logOpts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareCapabilities(config) {
|
||||
var allowed = $scope.formValues.capabilities.filter(function(item) {return item.allowed === true;});
|
||||
var notAllowed = $scope.formValues.capabilities.filter(function(item) {return item.allowed === false;});
|
||||
|
||||
var getCapName = function(item) {return item.capability;};
|
||||
config.HostConfig.CapAdd = allowed.map(getCapName);
|
||||
config.HostConfig.CapDrop = notAllowed.map(getCapName);
|
||||
}
|
||||
|
||||
function prepareConfiguration() {
|
||||
var config = angular.copy($scope.config);
|
||||
config.Cmd = ContainerHelper.commandStringToArray(config.Cmd);
|
||||
prepareNetworkConfig(config);
|
||||
prepareImageConfig(config);
|
||||
preparePortBindings(config);
|
||||
prepareConsole(config);
|
||||
prepareEnvironmentVariables(config);
|
||||
prepareVolumes(config);
|
||||
prepareLabels(config);
|
||||
prepareDevices(config);
|
||||
prepareResources(config);
|
||||
prepareLogDriver(config);
|
||||
prepareCapabilities(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
function loadFromContainerCmd() {
|
||||
if ($scope.config.Cmd) {
|
||||
$scope.config.Cmd = ContainerHelper.commandArrayToString($scope.config.Cmd);
|
||||
} else {
|
||||
$scope.config.Cmd = '';
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromContainerPortBindings() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.resetNetworkConfig = function() {
|
||||
$scope.config.NetworkingConfig = {
|
||||
EndpointsConfig: {}
|
||||
};
|
||||
};
|
||||
|
||||
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];
|
||||
// Mac Address
|
||||
if(Object.keys(d.NetworkSettings.Networks).length) {
|
||||
var firstNetwork = d.NetworkSettings.Networks[Object.keys(d.NetworkSettings.Networks)[0]];
|
||||
$scope.formValues.MacAddress = firstNetwork.MacAddress;
|
||||
$scope.config.NetworkingConfig.EndpointsConfig[$scope.config.HostConfig.NetworkMode] = firstNetwork;
|
||||
$scope.extraNetworks = angular.copy(d.NetworkSettings.Networks);
|
||||
delete $scope.extraNetworks[Object.keys(d.NetworkSettings.Networks)[0]];
|
||||
} else {
|
||||
$scope.formValues.MacAddress = '';
|
||||
}
|
||||
|
||||
// ExtraHosts
|
||||
if ($scope.config.HostConfig.ExtraHosts) {
|
||||
var extraHosts = $scope.config.HostConfig.ExtraHosts;
|
||||
for (var i = 0; i < extraHosts.length; i++) {
|
||||
var host = extraHosts[i];
|
||||
$scope.formValues.ExtraHosts.push({ 'value': host });
|
||||
}
|
||||
$scope.config.HostConfig.ExtraHosts = [];
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromContainerEnvironmentVariables() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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 loadFromContainerResources(d) {
|
||||
if (d.HostConfig.NanoCpus) {
|
||||
$scope.formValues.CpuLimit = d.HostConfig.NanoCpus / 1000000000;
|
||||
}
|
||||
if (d.HostConfig.Memory) {
|
||||
$scope.formValues.MemoryLimit = d.HostConfig.Memory / 1024 / 1024;
|
||||
}
|
||||
if (d.HostConfig.MemoryReservation) {
|
||||
$scope.formValues.MemoryReservation = d.HostConfig.MemoryReservation / 1024 / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromContainerCapabilities(d) {
|
||||
if (d.HostConfig.CapAdd) {
|
||||
d.HostConfig.CapAdd.forEach(function(cap) {
|
||||
$scope.formValues.capabilities.push(new ContainerCapability(cap, true));
|
||||
});
|
||||
}
|
||||
if (d.HostConfig.CapDrop) {
|
||||
d.HostConfig.CapDrop.forEach(function(cap) {
|
||||
$scope.formValues.capabilities.push(new ContainerCapability(cap, false));
|
||||
});
|
||||
}
|
||||
|
||||
function hasCapability(item) {
|
||||
return item.capability === cap.capability;
|
||||
}
|
||||
|
||||
var capabilities = new ContainerCapabilities();
|
||||
for (var i = 0; i < capabilities.length; i++) {
|
||||
var cap = capabilities[i];
|
||||
if (!_.find($scope.formValues.capabilities, hasCapability)) {
|
||||
$scope.formValues.capabilities.push(cap);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.formValues.capabilities.sort(function(a, b) {
|
||||
return a.capability < b.capability ? -1 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
function loadFromContainerSpec() {
|
||||
// Get container
|
||||
Container.get({ id: $transition$.params().from }).$promise
|
||||
.then(function success(d) {
|
||||
var fromContainer = new ContainerDetailsViewModel(d);
|
||||
if (fromContainer.ResourceControl && fromContainer.ResourceControl.Public) {
|
||||
$scope.formValues.AccessControlData.AccessControlEnabled = false;
|
||||
}
|
||||
$scope.fromContainer = fromContainer;
|
||||
$scope.config = ContainerHelper.configFromContainer(fromContainer.Model);
|
||||
loadFromContainerCmd(d);
|
||||
loadFromContainerLogging(d);
|
||||
loadFromContainerPortBindings(d);
|
||||
loadFromContainerVolumes(d);
|
||||
loadFromContainerNetworkConfig(d);
|
||||
loadFromContainerEnvironmentVariables(d);
|
||||
loadFromContainerLabels(d);
|
||||
loadFromContainerConsole(d);
|
||||
loadFromContainerDevices(d);
|
||||
loadFromContainerImageConfig(d);
|
||||
loadFromContainerResources(d);
|
||||
loadFromContainerCapabilities(d);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve container');
|
||||
});
|
||||
}
|
||||
|
||||
function loadFromContainerLogging(config) {
|
||||
var logConfig = config.HostConfig.LogConfig;
|
||||
$scope.formValues.LogDriverName = logConfig.Type;
|
||||
$scope.formValues.LogDriverOpts = _.map(logConfig.Config, function (value, name) {
|
||||
return {
|
||||
name: name,
|
||||
value: value
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
var nodeName = $transition$.params().nodeName;
|
||||
$scope.formValues.NodeName = nodeName;
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve volumes');
|
||||
});
|
||||
|
||||
var provider = $scope.applicationState.endpoint.mode.provider;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
NetworkService.networks(
|
||||
provider === 'DOCKER_STANDALONE' || provider === 'DOCKER_SWARM_MODE',
|
||||
false,
|
||||
provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
|
||||
)
|
||||
.then(function success(data) {
|
||||
var networks = data;
|
||||
networks.push({ Name: 'container' });
|
||||
$scope.availableNetworks = networks;
|
||||
|
||||
if (_.find(networks, {'Name': 'nat'})) {
|
||||
$scope.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve networks');
|
||||
});
|
||||
|
||||
Container.query({}, function (d) {
|
||||
var containers = d;
|
||||
$scope.runningContainers = containers;
|
||||
if ($transition$.params().from) {
|
||||
loadFromContainerSpec();
|
||||
} else {
|
||||
$scope.fromContainer = {};
|
||||
$scope.formValues.Registry = {};
|
||||
$scope.formValues.capabilities = new ContainerCapabilities();
|
||||
}
|
||||
}, function(e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
||||
});
|
||||
|
||||
SystemService.info()
|
||||
.then(function success(data) {
|
||||
$scope.availableRuntimes = Object.keys(data.Runtimes);
|
||||
$scope.config.HostConfig.Runtime = '';
|
||||
$scope.state.sliderMaxCpu = 32;
|
||||
if (data.NCPU) {
|
||||
$scope.state.sliderMaxCpu = data.NCPU;
|
||||
}
|
||||
$scope.state.sliderMaxMemory = 32768;
|
||||
if (data.MemTotal) {
|
||||
$scope.state.sliderMaxMemory = Math.floor(data.MemTotal / 1000 / 1000);
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve engine details');
|
||||
});
|
||||
|
||||
SettingsService.publicSettings()
|
||||
.then(function success(data) {
|
||||
$scope.allowBindMounts = data.AllowBindMountsForRegularUsers;
|
||||
$scope.allowPrivilegedMode = data.AllowPrivilegedModeForRegularUsers;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
});
|
||||
|
||||
PluginService.loggingPlugins(apiVersion < 1.25)
|
||||
.then(function success(loggingDrivers) {
|
||||
$scope.availableLoggingDrivers = loggingDrivers;
|
||||
});
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.isAdmin = userDetails.role === 1;
|
||||
}
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function create() {
|
||||
var oldContainer = null;
|
||||
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader($scope.formValues.NodeName);
|
||||
return findCurrentContainer()
|
||||
.then(setOldContainer)
|
||||
.then(confirmCreateContainer)
|
||||
.then(startCreationProcess)
|
||||
.catch(notifyOnError)
|
||||
.finally(final);
|
||||
|
||||
function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
}
|
||||
|
||||
function setOldContainer(container) {
|
||||
oldContainer = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
function findCurrentContainer() {
|
||||
return Container.query({ all: 1, filters: { name: ['^/' + $scope.config.name + '$'] } })
|
||||
.$promise
|
||||
.then(function onQuerySuccess(containers) {
|
||||
if (!containers.length) {
|
||||
return;
|
||||
}
|
||||
return containers[0];
|
||||
})
|
||||
.catch(notifyOnError);
|
||||
|
||||
function notifyOnError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve containers');
|
||||
}
|
||||
}
|
||||
|
||||
function startCreationProcess(confirmed) {
|
||||
if (!confirmed) {
|
||||
return $q.when();
|
||||
}
|
||||
if (!validateAccessControl()) {
|
||||
return $q.when();
|
||||
}
|
||||
$scope.state.actionInProgress = true;
|
||||
return pullImageIfNeeded()
|
||||
.then(stopAndRenameContainer)
|
||||
.then(createNewContainer)
|
||||
.then(applyResourceControl)
|
||||
.then(connectToExtraNetworks)
|
||||
.then(removeOldContainer)
|
||||
.then(onSuccess)
|
||||
.catch(onCreationProcessFail);
|
||||
}
|
||||
|
||||
function onCreationProcessFail(error) {
|
||||
var deferred = $q.defer();
|
||||
removeNewContainer()
|
||||
.then(restoreOldContainerName)
|
||||
.then(function() {
|
||||
deferred.reject(error);
|
||||
})
|
||||
.catch(function(restoreError) {
|
||||
deferred.reject(restoreError);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function removeNewContainer() {
|
||||
return findCurrentContainer().then(function onContainerLoaded(container) {
|
||||
if (container && (!oldContainer || container.Id !== oldContainer.Id)) {
|
||||
return ContainerService.remove(container, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function restoreOldContainerName() {
|
||||
if (!oldContainer) {
|
||||
return;
|
||||
}
|
||||
return ContainerService.renameContainer(oldContainer.Id, oldContainer.Names[0].substring(1));
|
||||
}
|
||||
|
||||
function confirmCreateContainer(container) {
|
||||
if (!container) {
|
||||
return $q.when(true);
|
||||
}
|
||||
|
||||
return showConfirmationModal();
|
||||
|
||||
function showConfirmationModal() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
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) {
|
||||
deferred.resolve(confirmed);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
function stopAndRenameContainer() {
|
||||
if (!oldContainer) {
|
||||
return $q.when();
|
||||
}
|
||||
return stopContainerIfNeeded(oldContainer)
|
||||
.then(renameContainer);
|
||||
}
|
||||
|
||||
function stopContainerIfNeeded(oldContainer) {
|
||||
if (oldContainer.State !== 'running') {
|
||||
return $q.when();
|
||||
}
|
||||
return ContainerService.stopContainer(oldContainer.Id);
|
||||
}
|
||||
|
||||
function renameContainer() {
|
||||
return ContainerService.renameContainer(oldContainer.Id, oldContainer.Names[0].substring(1) + '-old');
|
||||
}
|
||||
|
||||
function pullImageIfNeeded() {
|
||||
return $q.when($scope.formValues.alwaysPull &&
|
||||
ImageService.pullImage($scope.config.Image, $scope.formValues.Registry, true));
|
||||
}
|
||||
|
||||
function createNewContainer() {
|
||||
var config = prepareConfiguration();
|
||||
return ContainerService.createAndStartContainer(config);
|
||||
}
|
||||
|
||||
function applyResourceControl(newContainer) {
|
||||
var containerIdentifier = newContainer.Id;
|
||||
var userId = Authentication.getUserDetails().ID;
|
||||
|
||||
return $q.when(ResourceControlService.applyResourceControl(
|
||||
'container',
|
||||
containerIdentifier,
|
||||
userId,
|
||||
$scope.formValues.AccessControlData, []
|
||||
)).then(function onApplyResourceControlSuccess() {
|
||||
return containerIdentifier;
|
||||
});
|
||||
}
|
||||
|
||||
function connectToExtraNetworks(newContainerId) {
|
||||
if (!$scope.extraNetworks) {
|
||||
return $q.when();
|
||||
}
|
||||
|
||||
var connectionPromises = Object.keys($scope.extraNetworks).map(function (networkName) {
|
||||
return NetworkService.connectContainer(networkName, newContainerId);
|
||||
});
|
||||
|
||||
return $q.all(connectionPromises);
|
||||
}
|
||||
|
||||
function removeOldContainer() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (!oldContainer) {
|
||||
deferred.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
ContainerService.remove(oldContainer, true)
|
||||
.then(notifyOnRemoval)
|
||||
.catch(notifyOnRemoveError);
|
||||
|
||||
return deferred.promise;
|
||||
|
||||
function notifyOnRemoval() {
|
||||
Notifications.success('Container Removed', oldContainer.Id);
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
function notifyOnRemoveError(err) {
|
||||
deferred.reject({ msg: 'Unable to remove container', err: err });
|
||||
}
|
||||
}
|
||||
|
||||
function notifyOnError(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create container');
|
||||
}
|
||||
|
||||
function validateAccessControl() {
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1;
|
||||
|
||||
return validateForm(accessControlData, isAdmin);
|
||||
}
|
||||
|
||||
function onSuccess() {
|
||||
Notifications.success('Container successfully created');
|
||||
$state.go('docker.containers', {}, { reload: true });
|
||||
}
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -0,0 +1,188 @@
|
|||
export default function prepareConfiguration() {
|
||||
let prepareNetworkConfig = (config) => {
|
||||
var mode = config.HostConfig.NetworkMode;
|
||||
var container = this.formValues.NetworkContainer;
|
||||
var containerName = container;
|
||||
if (container && typeof container === 'object') {
|
||||
containerName = this.$filter('trimcontainername')(container.Names[0]);
|
||||
}
|
||||
var networkMode = mode;
|
||||
if (containerName) {
|
||||
networkMode += ':' + containerName;
|
||||
config.Hostname = '';
|
||||
}
|
||||
config.HostConfig.NetworkMode = networkMode;
|
||||
config.MacAddress = this.formValues.MacAddress;
|
||||
|
||||
config.NetworkingConfig.EndpointsConfig[networkMode] = {
|
||||
IPAMConfig: {
|
||||
IPv4Address: this.formValues.IPv4,
|
||||
IPv6Address: this.formValues.IPv6
|
||||
}
|
||||
};
|
||||
|
||||
for (const v of this.formValues.ExtraHosts) {
|
||||
if (v.value) {
|
||||
config.HostConfig.ExtraHosts.push(v.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let prepareImageConfig = (config) => {
|
||||
var image = config.Image;
|
||||
var registry = this.formValues.Registry;
|
||||
var imageConfig = this.ImageHelper.createImageConfigForContainer(image, registry.URL);
|
||||
config.Image = imageConfig.fromImage + ':' + imageConfig.tag;
|
||||
this.imageConfig = imageConfig;
|
||||
};
|
||||
|
||||
let preparePortBindings = (config) => {
|
||||
var bindings = {};
|
||||
for (const portBinding of config.HostConfig.PortBindings) {
|
||||
if (portBinding.containerPort) {
|
||||
var key = portBinding.containerPort + '/' + portBinding.protocol;
|
||||
var binding = {};
|
||||
if (portBinding.hostPort && portBinding.hostPort.indexOf(':') > -1) {
|
||||
var hostAndPort = portBinding.hostPort.split(':');
|
||||
binding.HostIp = hostAndPort[0];
|
||||
binding.HostPort = hostAndPort[1];
|
||||
} else {
|
||||
binding.HostPort = portBinding.hostPort;
|
||||
}
|
||||
bindings[key] = [binding];
|
||||
config.ExposedPorts[key] = {};
|
||||
}
|
||||
}
|
||||
config.HostConfig.PortBindings = bindings;
|
||||
};
|
||||
|
||||
let prepareConsole = (config) => {
|
||||
var value = this.formValues.Console;
|
||||
var openStdin = true;
|
||||
var tty = true;
|
||||
if (value === 'tty') {
|
||||
openStdin = false;
|
||||
} else if (value === 'interactive') {
|
||||
tty = false;
|
||||
} else if (value === 'none') {
|
||||
openStdin = false;
|
||||
tty = false;
|
||||
}
|
||||
config.OpenStdin = openStdin;
|
||||
config.Tty = tty;
|
||||
};
|
||||
|
||||
let prepareEnvironmentVariables = (config) => {
|
||||
var env = [];
|
||||
for (const v of config.Env) {
|
||||
if (v.name && v.value) {
|
||||
env.push(v.name + '=' + v.value);
|
||||
}
|
||||
}
|
||||
config.Env = env;
|
||||
};
|
||||
|
||||
let prepareVolumes = (config) => {
|
||||
var binds = [];
|
||||
var volumes = {};
|
||||
|
||||
for (const volume of this.formValues.Volumes) {
|
||||
var name = volume.name;
|
||||
var containerPath = volume.containerPath;
|
||||
if (name && containerPath) {
|
||||
var bind = name + ':' + containerPath;
|
||||
volumes[containerPath] = {};
|
||||
if (volume.readOnly) {
|
||||
bind += ':ro';
|
||||
}
|
||||
binds.push(bind);
|
||||
}
|
||||
}
|
||||
config.HostConfig.Binds = binds;
|
||||
config.Volumes = volumes;
|
||||
};
|
||||
|
||||
let prepareLabels = (config) => {
|
||||
var labels = {};
|
||||
for (const label of this.formValues.Labels) {
|
||||
if (label.name && label.value) {
|
||||
labels[label.name] = label.value;
|
||||
}
|
||||
}
|
||||
config.Labels = labels;
|
||||
};
|
||||
|
||||
let prepareDevices = (config) => {
|
||||
var path = [];
|
||||
for (const p of config.HostConfig.Devices) {
|
||||
if (p.pathOnHost) {
|
||||
if(p.pathInContainer === '') {
|
||||
p.pathInContainer = p.pathOnHost;
|
||||
}
|
||||
path.push({PathOnHost:p.pathOnHost,PathInContainer:p.pathInContainer,CgroupPermissions:'rwm'});
|
||||
}
|
||||
}
|
||||
config.HostConfig.Devices = path;
|
||||
};
|
||||
|
||||
let prepareResources = (config) => {
|
||||
// Memory Limit - Round to 0.125
|
||||
var memoryLimit = (Math.round(this.formValues.MemoryLimit * 8) / 8).toFixed(3);
|
||||
memoryLimit *= 1024 * 1024;
|
||||
if (memoryLimit > 0) {
|
||||
config.HostConfig.Memory = memoryLimit;
|
||||
}
|
||||
// Memory Resevation - Round to 0.125
|
||||
var memoryReservation = (Math.round(this.formValues.MemoryReservation * 8) / 8).toFixed(3);
|
||||
memoryReservation *= 1024 * 1024;
|
||||
if (memoryReservation > 0) {
|
||||
config.HostConfig.MemoryReservation = memoryReservation;
|
||||
}
|
||||
// CPU Limit
|
||||
if (this.formValues.CpuLimit > 0) {
|
||||
config.HostConfig.NanoCpus = this.formValues.CpuLimit * 1000000000;
|
||||
}
|
||||
};
|
||||
|
||||
let prepareLogDriver = (config) => {
|
||||
var logOpts = {};
|
||||
if (this.formValues.LogDriverName) {
|
||||
config.HostConfig.LogConfig = { Type: this.formValues.LogDriverName };
|
||||
if (this.formValues.LogDriverName !== 'none') {
|
||||
for (const opt of this.formValues.LogDriverOpts) {
|
||||
if (opt.name) {
|
||||
logOpts[opt.name] = opt.value;
|
||||
}
|
||||
}
|
||||
if (Object.keys(logOpts).length !== 0 && logOpts.constructor === Object) {
|
||||
config.HostConfig.LogConfig.Config = logOpts;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let prepareCapabilities = (config) => {
|
||||
var allowed = this.formValues.capabilities.filter(function(item) {return item.allowed === true;});
|
||||
var notAllowed = this.formValues.capabilities.filter(function(item) {return item.allowed === false;});
|
||||
|
||||
var getCapName = function(item) {return item.capability;};
|
||||
config.HostConfig.CapAdd = allowed.map(getCapName);
|
||||
config.HostConfig.CapDrop = notAllowed.map(getCapName);
|
||||
};
|
||||
|
||||
var config = angular.copy(this.config);
|
||||
config.Cmd = this.ContainerHelper.commandStringToArray(config.Cmd);
|
||||
prepareNetworkConfig(config);
|
||||
prepareImageConfig(config);
|
||||
preparePortBindings(config);
|
||||
prepareConsole(config);
|
||||
prepareEnvironmentVariables(config);
|
||||
prepareVolumes(config);
|
||||
prepareLabels(config);
|
||||
prepareDevices(config);
|
||||
prepareResources(config);
|
||||
prepareLogDriver(config);
|
||||
prepareCapabilities(config);
|
||||
return config;
|
||||
}
|
||||
|
Loading…
Reference in New Issue