mirror of https://github.com/portainer/portainer
refactor(templates): refactor controller code and create required services (#580)
parent
06a484880b
commit
2f3475b96a
|
@ -10,7 +10,7 @@
|
||||||
<div id="selectedTemplate" class="row" ng-if="state.selectedTemplate">
|
<div id="selectedTemplate" class="row" ng-if="state.selectedTemplate">
|
||||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-custom-header icon="state.selectedTemplate.logo" title="state.selectedTemplate.image">
|
<rd-widget-custom-header icon="state.selectedTemplate.Logo" title="state.selectedTemplate.Image">
|
||||||
</rd-widget-custom-header>
|
</rd-widget-custom-header>
|
||||||
<rd-widget-body classes="padding">
|
<rd-widget-body classes="padding">
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
|
@ -27,9 +27,9 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- name-and-network-inputs -->
|
<!-- name-and-network-inputs -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="image_registry" class="col-sm-2 control-label text-left">Name</label>
|
<label for="container_name" class="col-sm-2 control-label text-left">Name</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input type="text" class="form-control" ng-model="formValues.name" placeholder="e.g. web (optional)">
|
<input type="text" name="container_name" class="form-control" ng-model="formValues.name" placeholder="e.g. web (optional)">
|
||||||
</div>
|
</div>
|
||||||
<label for="container_network" class="col-sm-2 control-label text-right">Network</label>
|
<label for="container_network" class="col-sm-2 control-label text-right">Network</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !name-and-network-inputs -->
|
<!-- !name-and-network-inputs -->
|
||||||
<div ng-repeat="var in state.selectedTemplate.env" ng-if="!var.set" class="form-group">
|
<div ng-repeat="var in state.selectedTemplate.Env" ng-if="!var.set" class="form-group">
|
||||||
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">{{ var.label }}</label>
|
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">{{ var.label }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="var.value">
|
<select ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="var.value">
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- port-mapping-input-list -->
|
<!-- port-mapping-input-list -->
|
||||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||||
<div ng-repeat="portBinding in formValues.ports" style="margin-top: 2px;">
|
<div ng-repeat="portBinding in state.selectedTemplate.Ports" style="margin-top: 2px;">
|
||||||
<div class="input-group col-sm-5 input-group-sm">
|
<div class="input-group col-sm-5 input-group-sm">
|
||||||
<span class="input-group-addon">host</span>
|
<span class="input-group-addon">host</span>
|
||||||
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)">
|
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)">
|
||||||
|
@ -125,9 +125,9 @@
|
||||||
<rd-widget-body classes="padding">
|
<rd-widget-body classes="padding">
|
||||||
<div class="template-list">
|
<div class="template-list">
|
||||||
<div dir-paginate="tpl in templates | itemsPerPage: state.pagination_count" class="container-template hvr-underline-from-center" id="template_{{ tpl.index }}" ng-click="selectTemplate(tpl.index)">
|
<div dir-paginate="tpl in templates | itemsPerPage: state.pagination_count" class="container-template hvr-underline-from-center" id="template_{{ tpl.index }}" ng-click="selectTemplate(tpl.index)">
|
||||||
<img class="logo" ng-src="{{ tpl.logo }}" />
|
<img class="logo" ng-src="{{ tpl.Logo }}" />
|
||||||
<div class="title">{{ tpl.title }}</div>
|
<div class="title">{{ tpl.Title }}</div>
|
||||||
<div class="description">{{ tpl.description }}</div>
|
<div class="description">{{ tpl.Description }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="!templates" class="text-center text-muted">
|
<div ng-if="!templates" class="text-center text-muted">
|
||||||
Loading...
|
Loading...
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
angular.module('templates', [])
|
angular.module('templates', [])
|
||||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$filter', '$anchorScroll', 'Config', 'Info', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'Network', 'Templates', 'TemplateHelper', 'Messages', 'Pagination',
|
.controller('TemplatesController', ['$scope', '$q', '$state', '$anchorScroll', 'Config', 'ContainerService', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Messages', 'Pagination',
|
||||||
function ($scope, $q, $state, $filter, $anchorScroll, Config, Info, Container, ContainerHelper, Image, ImageHelper, Volume, Network, Templates, TemplateHelper, Messages, Pagination) {
|
function ($scope, $q, $state, $anchorScroll, Config, ContainerService, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Messages, Pagination) {
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
selectedTemplate: null,
|
selectedTemplate: null,
|
||||||
showAdvancedOptions: false,
|
showAdvancedOptions: false,
|
||||||
|
@ -9,233 +9,118 @@ function ($scope, $q, $state, $filter, $anchorScroll, Config, Info, Container, C
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
network: "",
|
network: "",
|
||||||
name: "",
|
name: "",
|
||||||
ports: []
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var selectedItem = -1;
|
|
||||||
|
|
||||||
$scope.changePaginationCount = function() {
|
$scope.changePaginationCount = function() {
|
||||||
Pagination.setPaginationCount('templates', $scope.state.pagination_count);
|
Pagination.setPaginationCount('templates', $scope.state.pagination_count);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addPortBinding = function() {
|
$scope.addPortBinding = function() {
|
||||||
$scope.formValues.ports.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
$scope.state.selectedTemplate.Ports.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removePortBinding = function(index) {
|
$scope.removePortBinding = function(index) {
|
||||||
$scope.formValues.ports.splice(index, 1);
|
$scope.state.selectedTemplate.Ports.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: centralize, already present in createContainerController
|
|
||||||
function createContainer(config) {
|
|
||||||
Container.create(config, function (d) {
|
|
||||||
if (d.message) {
|
|
||||||
$('#createContainerSpinner').hide();
|
|
||||||
Messages.error('Error', {}, d.message);
|
|
||||||
} else {
|
|
||||||
Container.start({id: d.Id}, {}, function (cd) {
|
|
||||||
if (cd.message) {
|
|
||||||
$('#createContainerSpinner').hide();
|
|
||||||
Messages.error('Error', {}, cd.message);
|
|
||||||
} else {
|
|
||||||
$('#createContainerSpinner').hide();
|
|
||||||
Messages.send('Container Started', d.Id);
|
|
||||||
$state.go('containers', {}, {reload: true});
|
|
||||||
}
|
|
||||||
}, function (e) {
|
|
||||||
$('#createContainerSpinner').hide();
|
|
||||||
Messages.error("Failure", e, 'Unable to start container');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, function (e) {
|
|
||||||
$('#createContainerSpinner').hide();
|
|
||||||
Messages.error("Failure", e, 'Unable to create container');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: centralize, already present in createContainerController
|
|
||||||
function pullImageAndCreateContainer(imageConfig, containerConfig) {
|
|
||||||
Image.create(imageConfig, function (data) {
|
|
||||||
var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error');
|
|
||||||
if (err) {
|
|
||||||
var detail = data[data.length - 1];
|
|
||||||
$('#createContainerSpinner').hide();
|
|
||||||
Messages.error("Error", {}, detail.error);
|
|
||||||
} else {
|
|
||||||
createContainer(containerConfig);
|
|
||||||
}
|
|
||||||
}, function (e) {
|
|
||||||
$('#createContainerSpinner').hide();
|
|
||||||
Messages.error("Failure", e, "Unable to pull image");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInitialConfiguration() {
|
|
||||||
return {
|
|
||||||
Env: [],
|
|
||||||
OpenStdin: false,
|
|
||||||
Tty: false,
|
|
||||||
ExposedPorts: {},
|
|
||||||
HostConfig: {
|
|
||||||
RestartPolicy: {
|
|
||||||
Name: 'no'
|
|
||||||
},
|
|
||||||
PortBindings: {},
|
|
||||||
Binds: [],
|
|
||||||
NetworkMode: $scope.formValues.network.Name,
|
|
||||||
Privileged: false
|
|
||||||
},
|
|
||||||
Volumes: {},
|
|
||||||
name: $scope.formValues.name
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function preparePortBindings(config, ports) {
|
|
||||||
var bindings = {};
|
|
||||||
ports.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 createConfigFromTemplate(template) {
|
|
||||||
var containerConfig = getInitialConfiguration();
|
|
||||||
containerConfig.Image = template.image;
|
|
||||||
if (template.env) {
|
|
||||||
template.env.forEach(function (v) {
|
|
||||||
if (v.value || v.set) {
|
|
||||||
var val;
|
|
||||||
if (v.type && v.type === 'container') {
|
|
||||||
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM' && $scope.formValues.network.Scope === 'global') {
|
|
||||||
val = $filter('swarmcontainername')(v.value);
|
|
||||||
} else {
|
|
||||||
var container = v.value;
|
|
||||||
val = container.NetworkSettings.Networks[Object.keys(container.NetworkSettings.Networks)[0]].IPAddress;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val = v.set ? v.set : v.value;
|
|
||||||
}
|
|
||||||
containerConfig.Env.push(v.name + "=" + val);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
preparePortBindings(containerConfig, $scope.formValues.ports);
|
|
||||||
prepareImageConfig(containerConfig, template);
|
|
||||||
return containerConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareImageConfig(config, template) {
|
|
||||||
var image = template.image;
|
|
||||||
var registry = template.registry || '';
|
|
||||||
var imageConfig = ImageHelper.createImageConfigForContainer(image, registry);
|
|
||||||
config.Image = imageConfig.fromImage + ':' + imageConfig.tag;
|
|
||||||
$scope.imageConfig = imageConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareVolumeQueries(template, containerConfig) {
|
|
||||||
var volumeQueries = [];
|
|
||||||
if (template.volumes) {
|
|
||||||
template.volumes.forEach(function (vol) {
|
|
||||||
volumeQueries.push(
|
|
||||||
Volume.create({}, function (d) {
|
|
||||||
if (d.message) {
|
|
||||||
Messages.error("Unable to create volume", {}, d.message);
|
|
||||||
} else {
|
|
||||||
Messages.send("Volume created", d.Name);
|
|
||||||
containerConfig.Volumes[vol] = {};
|
|
||||||
containerConfig.HostConfig.Binds.push(d.Name + ':' + vol);
|
|
||||||
}
|
|
||||||
}, function (e) {
|
|
||||||
Messages.error("Failure", e, "Unable to create volume");
|
|
||||||
}).$promise
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return volumeQueries;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.createTemplate = function() {
|
$scope.createTemplate = function() {
|
||||||
$('#createContainerSpinner').show();
|
$('#createContainerSpinner').show();
|
||||||
var template = $scope.state.selectedTemplate;
|
var template = $scope.state.selectedTemplate;
|
||||||
var containerConfig = createConfigFromTemplate(template);
|
var templateConfiguration = createTemplateConfiguration(template);
|
||||||
var createVolumeQueries = prepareVolumeQueries(template, containerConfig);
|
|
||||||
$q.all(createVolumeQueries).then(function (d) {
|
VolumeService.createAutoGeneratedLocalVolumes(template.Volumes)
|
||||||
pullImageAndCreateContainer($scope.imageConfig, containerConfig);
|
.then(function success(data) {
|
||||||
|
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration.container, template, data);
|
||||||
|
return ImageService.pullImage(templateConfiguration.image);
|
||||||
|
})
|
||||||
|
.then(function success(data) {
|
||||||
|
return ContainerService.createAndStartContainer(templateConfiguration.container);
|
||||||
|
})
|
||||||
|
.then(function success(data) {
|
||||||
|
Messages.send('Container Started', data.Id);
|
||||||
|
$state.go('containers', {}, {reload: true});
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
Messages.error('Failure', err, err.msg);
|
||||||
|
})
|
||||||
|
.finally(function final() {
|
||||||
|
$('#createContainerSpinner').hide();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.selectTemplate = function(id) {
|
var selectedItem = -1;
|
||||||
$('#template_' + id).toggleClass("container-template--selected");
|
$scope.selectTemplate = function(idx) {
|
||||||
if (selectedItem === id) {
|
$('#template_' + idx).toggleClass("container-template--selected");
|
||||||
selectedItem = -1;
|
if (selectedItem === idx) {
|
||||||
$scope.state.selectedTemplate = null;
|
unselectTemplate();
|
||||||
} else {
|
} else {
|
||||||
$('#template_' + selectedItem).toggleClass("container-template--selected");
|
selectTemplate(idx);
|
||||||
selectedItem = id;
|
|
||||||
var selectedTemplate = $scope.templates[id];
|
|
||||||
$scope.state.selectedTemplate = selectedTemplate;
|
|
||||||
$scope.formValues.ports = selectedTemplate.ports ? TemplateHelper.getPortBindings(selectedTemplate.ports) : [];
|
|
||||||
$anchorScroll('selectedTemplate');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function unselectTemplate() {
|
||||||
|
selectedItem = -1;
|
||||||
|
$scope.state.selectedTemplate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectTemplate(idx) {
|
||||||
|
$('#template_' + selectedItem).toggleClass("container-template--selected");
|
||||||
|
selectedItem = idx;
|
||||||
|
var selectedTemplate = $scope.templates[idx];
|
||||||
|
$scope.state.selectedTemplate = selectedTemplate;
|
||||||
|
$anchorScroll('selectedTemplate');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTemplateConfiguration(template) {
|
||||||
|
var network = $scope.formValues.network;
|
||||||
|
var name = $scope.formValues.name;
|
||||||
|
var containerMapping = determineContainerMapping(network);
|
||||||
|
return TemplateService.createTemplateConfiguration(template, name, network, containerMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineContainerMapping(network) {
|
||||||
|
var endpointProvider = $scope.applicationState.endpoint.mode.provider;
|
||||||
|
var containerMapping = 'BY_CONTAINER_IP';
|
||||||
|
if (endpointProvider === 'DOCKER_SWARM' && network.Scope === 'global') {
|
||||||
|
containerMapping = 'BY_SWARM_CONTAINER_NAME';
|
||||||
|
} else if (network.Name !== "bridge") {
|
||||||
|
containerMapping = 'BY_CONTAINER_NAME';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterNetworksBasedOnProvider(networks) {
|
||||||
|
var endpointProvider = $scope.applicationState.endpoint.mode.provider;
|
||||||
|
if (endpointProvider === 'DOCKER_SWARM' || endpointProvider === 'DOCKER_SWARM_MODE') {
|
||||||
|
networks = NetworkService.filterGlobalNetworks(networks);
|
||||||
|
$scope.globalNetworkCount = networks.length;
|
||||||
|
NetworkService.addPredefinedLocalNetworks(networks);
|
||||||
|
} else {
|
||||||
|
$scope.formValues.network = _.find(networks, function(o) { return o.Name === "bridge"; });
|
||||||
|
}
|
||||||
|
return networks;
|
||||||
|
}
|
||||||
|
|
||||||
function initTemplates() {
|
function initTemplates() {
|
||||||
Templates.get(function (data) {
|
Config.$promise.then(function (c) {
|
||||||
$scope.templates = data.map(function(tpl,index){
|
$q.all({
|
||||||
tpl.index = index;
|
templates: TemplateService.getTemplates(),
|
||||||
return tpl;
|
containers: ContainerService.getContainers(0, c.hiddenLabels),
|
||||||
|
networks: NetworkService.getNetworks()
|
||||||
|
})
|
||||||
|
.then(function success(data) {
|
||||||
|
$scope.templates = data.templates;
|
||||||
|
$scope.runningContainers = data.containers;
|
||||||
|
$scope.availableNetworks = filterNetworksBasedOnProvider(data.networks);
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
$scope.templates = [];
|
||||||
|
Messages.error("Failure", err, "An error occured during apps initialization.");
|
||||||
|
})
|
||||||
|
.finally(function final(){
|
||||||
|
$('#loadTemplatesSpinner').hide();
|
||||||
});
|
});
|
||||||
$('#loadTemplatesSpinner').hide();
|
|
||||||
}, function (e) {
|
|
||||||
$('#loadTemplatesSpinner').hide();
|
|
||||||
Messages.error("Failure", e, "Unable to retrieve apps list");
|
|
||||||
$scope.templates = [];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.$promise.then(function (c) {
|
initTemplates();
|
||||||
var containersToHideLabels = c.hiddenLabels;
|
|
||||||
Network.query({}, function (d) {
|
|
||||||
var networks = d;
|
|
||||||
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
|
|
||||||
networks = d.filter(function (network) {
|
|
||||||
if (network.Scope === 'global') {
|
|
||||||
return network;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$scope.globalNetworkCount = networks.length;
|
|
||||||
networks.push({Scope: "local", Name: "bridge"});
|
|
||||||
networks.push({Scope: "local", Name: "host"});
|
|
||||||
networks.push({Scope: "local", Name: "none"});
|
|
||||||
} else {
|
|
||||||
$scope.formValues.network = _.find(networks, function(o) { return o.Name === "bridge"; });
|
|
||||||
}
|
|
||||||
$scope.availableNetworks = networks;
|
|
||||||
}, function (e) {
|
|
||||||
Messages.error("Failure", e, "Unable to retrieve networks");
|
|
||||||
});
|
|
||||||
Container.query({all: 0}, function (d) {
|
|
||||||
var containers = d;
|
|
||||||
if (containersToHideLabels) {
|
|
||||||
containers = ContainerHelper.hideContainers(d, containersToHideLabels);
|
|
||||||
}
|
|
||||||
$scope.runningContainers = containers;
|
|
||||||
}, function (e) {
|
|
||||||
Messages.error("Failure", e, "Unable to retrieve running containers");
|
|
||||||
});
|
|
||||||
initTemplates();
|
|
||||||
});
|
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -1,40 +1,70 @@
|
||||||
angular.module('portainer.helpers')
|
angular.module('portainer.helpers')
|
||||||
.factory('TemplateHelper', [function TemplateHelperFactory() {
|
.factory('TemplateHelper', ['$filter', function TemplateHelperFactory($filter) {
|
||||||
'use strict';
|
'use strict';
|
||||||
return {
|
var helper = {};
|
||||||
getPortBindings: function(ports) {
|
|
||||||
var bindings = [];
|
helper.getDefaultContainerConfiguration = function() {
|
||||||
ports.forEach(function (port) {
|
return {
|
||||||
var portAndProtocol = _.split(port, '/');
|
Env: [],
|
||||||
var binding = {
|
OpenStdin: false,
|
||||||
containerPort: portAndProtocol[0],
|
Tty: false,
|
||||||
protocol: portAndProtocol[1]
|
ExposedPorts: {},
|
||||||
};
|
HostConfig: {
|
||||||
bindings.push(binding);
|
RestartPolicy: {
|
||||||
});
|
Name: 'no'
|
||||||
return bindings;
|
},
|
||||||
},
|
PortBindings: {},
|
||||||
//Not used atm, may prove useful later
|
Binds: [],
|
||||||
getVolumeBindings: function(volumes) {
|
Privileged: false
|
||||||
var bindings = [];
|
},
|
||||||
volumes.forEach(function (volume) {
|
Volumes: {}
|
||||||
bindings.push({ containerPath: volume });
|
};
|
||||||
});
|
|
||||||
return bindings;
|
|
||||||
},
|
|
||||||
//Not used atm, may prove useful later
|
|
||||||
getEnvBindings: function(env) {
|
|
||||||
var bindings = [];
|
|
||||||
env.forEach(function (envvar) {
|
|
||||||
var binding = {
|
|
||||||
name: envvar.name
|
|
||||||
};
|
|
||||||
if (envvar.set) {
|
|
||||||
binding.value = envvar.set;
|
|
||||||
}
|
|
||||||
bindings.push(binding);
|
|
||||||
});
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
helper.portArrayToPortConfiguration = function(ports) {
|
||||||
|
var portConfiguration = {
|
||||||
|
bindings: {},
|
||||||
|
exposedPorts: {}
|
||||||
|
};
|
||||||
|
ports.forEach(function (p) {
|
||||||
|
if (p.containerPort) {
|
||||||
|
var key = p.containerPort + "/" + p.protocol;
|
||||||
|
var binding = {};
|
||||||
|
if (p.hostPort) {
|
||||||
|
binding.HostPort = p.hostPort;
|
||||||
|
if (p.hostPort.indexOf(':') > -1) {
|
||||||
|
var hostAndPort = p.hostPort.split(':');
|
||||||
|
binding.HostIp = hostAndPort[0];
|
||||||
|
binding.HostPort = hostAndPort[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
portConfiguration.bindings[key] = [binding];
|
||||||
|
portConfiguration.exposedPorts[key] = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return portConfiguration;
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.EnvToStringArray = function(templateEnvironment, containerMapping) {
|
||||||
|
var env = [];
|
||||||
|
templateEnvironment.forEach(function(envvar) {
|
||||||
|
if (envvar.value || envvar.set) {
|
||||||
|
var value = envvar.set ? envvar.set : envvar.value;
|
||||||
|
if (envvar.type && envvar.type === 'container') {
|
||||||
|
if (containerMapping === 'BY_CONTAINER_IP') {
|
||||||
|
var container = envvar.value;
|
||||||
|
value = container.NetworkSettings.Networks[Object.keys(container.NetworkSettings.Networks)[0]].IPAddress;
|
||||||
|
} else if (containerMapping === 'BY_CONTAINER_NAME') {
|
||||||
|
value = $filter('containername')(envvar.value);
|
||||||
|
} else if (containerMapping === 'BY_SWARM_CONTAINER_NAME') {
|
||||||
|
value = $filter('swarmcontainername')(envvar.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env.push(envvar.name + "=" + value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return env;
|
||||||
|
};
|
||||||
|
|
||||||
|
return helper;
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
function TemplateViewModel(data) {
|
||||||
|
this.Title = data.title;
|
||||||
|
this.Description = data.description;
|
||||||
|
this.Logo = data.logo;
|
||||||
|
this.Image = data.image;
|
||||||
|
this.Registry = data.registry ? data.registry : '';
|
||||||
|
this.Env = data.env ? data.env : [];
|
||||||
|
this.Volumes = data.volumes ? data.volumes : [];
|
||||||
|
this.Ports = [];
|
||||||
|
if (data.ports) {
|
||||||
|
this.Ports = data.ports.map(function (p) {
|
||||||
|
var portAndProtocol = _.split(p, '/');
|
||||||
|
return {
|
||||||
|
containerPort: portAndProtocol[0],
|
||||||
|
protocol: portAndProtocol[1]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
angular.module('portainer.rest')
|
angular.module('portainer.rest')
|
||||||
.factory('Templates', ['$resource', 'TEMPLATES_ENDPOINT', function TemplatesFactory($resource, TEMPLATES_ENDPOINT) {
|
.factory('Template', ['$resource', 'TEMPLATES_ENDPOINT', function TemplateFactory($resource, TEMPLATES_ENDPOINT) {
|
||||||
return $resource(TEMPLATES_ENDPOINT, {}, {
|
return $resource(TEMPLATES_ENDPOINT, {}, {
|
||||||
get: {method: 'GET', isArray: true}
|
get: {method: 'GET', isArray: true}
|
||||||
});
|
});
|
|
@ -0,0 +1,71 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('ContainerService', ['$q', 'Container', 'ContainerHelper', function ContainerServiceFactory($q, Container, ContainerHelper) {
|
||||||
|
'use strict';
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
service.getContainers = function (all, hiddenLabels) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Container.query({ all: all }).$promise
|
||||||
|
.then(function success(data) {
|
||||||
|
var containers = data;
|
||||||
|
if (hiddenLabels) {
|
||||||
|
containers = ContainerHelper.hideContainers(d, hiddenLabels);
|
||||||
|
}
|
||||||
|
deferred.resolve(data);
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject({ msg: 'Unable to retriever containers', err: err });
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.createContainer = function(configuration) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Container.create(configuration).$promise
|
||||||
|
.then(function success(data) {
|
||||||
|
if (data.message) {
|
||||||
|
deferred.reject({ msg: data.message });
|
||||||
|
} else {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject({ msg: 'Unable to create container', err: err });
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.startContainer = function(containerID) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Container.start({ id: containerID }, {}).$promise
|
||||||
|
.then(function success(data) {
|
||||||
|
if (data.message) {
|
||||||
|
deferred.reject({ msg: data.message });
|
||||||
|
} else {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject({ msg: 'Unable to start container', err: err });
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.createAndStartContainer = function(configuration) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
var containerID;
|
||||||
|
service.createContainer(configuration)
|
||||||
|
.then(function success(data) {
|
||||||
|
containerID = data.Id;
|
||||||
|
return service.startContainer(containerID);
|
||||||
|
})
|
||||||
|
.then(function success() {
|
||||||
|
deferred.resolve({ Id: containerID });
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject(err);
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
return service;
|
||||||
|
}]);
|
|
@ -0,0 +1,24 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('ImageService', ['$q', 'Image', function ImageServiceFactory($q, Image) {
|
||||||
|
'use strict';
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
service.pullImage = function(imageConfiguration) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Image.create(imageConfiguration).$promise
|
||||||
|
.then(function success(data) {
|
||||||
|
var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error');
|
||||||
|
if (err) {
|
||||||
|
var detail = data[data.length - 1];
|
||||||
|
deferred.reject({ msg: detail.error });
|
||||||
|
} else {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject({ msg: 'Unable to pull image', err: err });
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
return service;
|
||||||
|
}]);
|
|
@ -0,0 +1,25 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('NetworkService', ['$q', 'Network', function NetworkServiceFactory($q, Network) {
|
||||||
|
'use strict';
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
service.getNetworks = function() {
|
||||||
|
return Network.query({}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.filterGlobalNetworks = function(networks) {
|
||||||
|
return networks.filter(function (network) {
|
||||||
|
if (network.Scope === 'global') {
|
||||||
|
return network;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
service.addPredefinedLocalNetworks = function(networks) {
|
||||||
|
networks.push({Scope: "local", Name: "bridge"});
|
||||||
|
networks.push({Scope: "local", Name: "host"});
|
||||||
|
networks.push({Scope: "local", Name: "none"});
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
|
@ -0,0 +1,59 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('TemplateService', ['$q', 'Template', 'TemplateHelper', 'ImageHelper', function TemplateServiceFactory($q, Template, TemplateHelper, ImageHelper) {
|
||||||
|
'use strict';
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
service.getTemplates = function() {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Template.get().$promise
|
||||||
|
.then(function success(data) {
|
||||||
|
var templates = data.map(function (tpl, idx) {
|
||||||
|
var template = new TemplateViewModel(tpl);
|
||||||
|
template.index = idx;
|
||||||
|
return template;
|
||||||
|
});
|
||||||
|
deferred.resolve(templates);
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject({ msg: 'Unable to retrieve templates', err: err });
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.createTemplateConfiguration = function(template, containerName, network, containerMapping) {
|
||||||
|
var imageConfiguration = service.createImageConfiguration(template);
|
||||||
|
var containerConfiguration = service.createContainerConfiguration(template, containerName, network, containerMapping);
|
||||||
|
containerConfiguration.Image = imageConfiguration.fromImage + ':' + imageConfiguration.tag;
|
||||||
|
return {
|
||||||
|
container: containerConfiguration,
|
||||||
|
image: imageConfiguration
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
service.createImageConfiguration = function(template) {
|
||||||
|
return ImageHelper.createImageConfigForContainer(template.Image, template.Registry);
|
||||||
|
};
|
||||||
|
|
||||||
|
service.createContainerConfiguration = function(template, containerName, network, containerMapping) {
|
||||||
|
var configuration = TemplateHelper.getDefaultContainerConfiguration();
|
||||||
|
configuration.HostConfig.NetworkMode = network.Name;
|
||||||
|
configuration.name = containerName;
|
||||||
|
configuration.Image = template.Image;
|
||||||
|
if (template.Env) {
|
||||||
|
configuration.Env = TemplateHelper.EnvToStringArray(template.Env, containerMapping);
|
||||||
|
}
|
||||||
|
var portConfiguration = TemplateHelper.portArrayToPortConfiguration(template.Ports);
|
||||||
|
configuration.HostConfig.PortBindings = portConfiguration.bindings;
|
||||||
|
configuration.ExposedPorts = portConfiguration.exposedPorts;
|
||||||
|
return configuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.updateContainerConfigurationWithVolumes = function(configuration, template, createdVolumes) {
|
||||||
|
createdVolumes.forEach(function (volume, idx) {
|
||||||
|
configuration.Volumes[template.Volumes[idx]] = {};
|
||||||
|
configuration.HostConfig.Binds.push(volume.Name + ':' + template.Volumes[idx]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
|
@ -0,0 +1,59 @@
|
||||||
|
angular.module('portainer.services')
|
||||||
|
.factory('VolumeService', ['$q', 'Volume', function VolumeServiceFactory($q, Volume) {
|
||||||
|
'use strict';
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
function prepareVolumeQueries(template, containerConfig) {
|
||||||
|
var volumeQueries = [];
|
||||||
|
if (template.volumes) {
|
||||||
|
template.volumes.forEach(function (vol) {
|
||||||
|
volumeQueries.push(
|
||||||
|
Volume.create({}, function (d) {
|
||||||
|
if (d.message) {
|
||||||
|
Messages.error("Unable to create volume", {}, d.message);
|
||||||
|
} else {
|
||||||
|
Messages.send("Volume created", d.Name);
|
||||||
|
containerConfig.Volumes[vol] = {};
|
||||||
|
containerConfig.HostConfig.Binds.push(d.Name + ':' + vol);
|
||||||
|
}
|
||||||
|
}, function (e) {
|
||||||
|
Messages.error("Failure", e, "Unable to create volume");
|
||||||
|
}).$promise
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return volumeQueries;
|
||||||
|
}
|
||||||
|
|
||||||
|
service.createVolume = function(volumeConfiguration) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
Volume.create(volumeConfiguration).$promise
|
||||||
|
.then(function success(data) {
|
||||||
|
if (data.message) {
|
||||||
|
deferred.reject({ msg: data.message });
|
||||||
|
} else {
|
||||||
|
deferred.resolve(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function error(err) {
|
||||||
|
deferred.reject({ msg: 'Unable to create volume', err: err });
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.createVolumes = function(volumes) {
|
||||||
|
var createVolumeQueries = volumes.map(function(volume) {
|
||||||
|
return service.createVolume(volume);
|
||||||
|
});
|
||||||
|
return $q.all(createVolumeQueries);
|
||||||
|
};
|
||||||
|
|
||||||
|
service.createAutoGeneratedLocalVolumes = function (volumes) {
|
||||||
|
var createVolumeQueries = volumes.map(function(volume) {
|
||||||
|
return service.createVolume({});
|
||||||
|
});
|
||||||
|
return $q.all(createVolumeQueries);
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
Loading…
Reference in New Issue