-
-
-
-
-
-
-
-
-
-
-
- {{ tpl.Title }}
-
-
-
-
-
-
-
-
-
-
-
- {{ tpl.Description }}
-
-
- {{ tpl.Categories.join(', ') }}
-
-
-
-
-
+
diff --git a/app/components/templates/templatesController.js b/app/components/templates/templatesController.js
index 38fc6a525..8ca34a100 100644
--- a/app/components/templates/templatesController.js
+++ b/app/components/templates/templatesController.js
@@ -1,14 +1,16 @@
angular.module('templates', [])
-.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService',
-function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication, FormValidator, SettingsService) {
+.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService',
+function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication, FormValidator, SettingsService, StackService) {
$scope.state = {
selectedTemplate: null,
showAdvancedOptions: false,
hideDescriptions: $transition$.params().hide_descriptions,
formValidationError: '',
+ showDeploymentSelector: false,
filters: {
Categories: '!',
- Platform: '!'
+ Platform: '!',
+ Type: 'container'
}
};
@@ -54,19 +56,7 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
return true;
}
- $scope.createTemplate = function() {
- $('#createContainerSpinner').show();
-
- var userDetails = Authentication.getUserDetails();
- var accessControlData = $scope.formValues.AccessControlData;
- var isAdmin = userDetails.role === 1 ? true : false;
-
- if (!validateForm(accessControlData, isAdmin)) {
- $('#createContainerSpinner').hide();
- return;
- }
-
- var template = $scope.state.selectedTemplate;
+ function createContainerFromTemplate(template, userId, accessControlData) {
var templateConfiguration = createTemplateConfiguration(template);
var generatedVolumeCount = TemplateHelper.determineRequiredGeneratedVolumeCount(template.Volumes);
var generatedVolumeIds = [];
@@ -85,7 +75,6 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
})
.then(function success(data) {
var containerIdentifier = data.Id;
- var userId = userDetails.ID;
return ResourceControlService.applyResourceControl('container', containerIdentifier, userId, accessControlData, generatedVolumeIds);
})
.then(function success() {
@@ -96,8 +85,59 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
Notifications.error('Failure', err, err.msg);
})
.finally(function final() {
- $('#createContainerSpinner').hide();
+ $('#createResourceSpinner').hide();
});
+ }
+
+ function createStackFromTemplate(template, userId, accessControlData) {
+ var stackName = $scope.formValues.name;
+
+ for (var i = 0; i < template.Env.length; i++) {
+ var envvar = template.Env[i];
+ if (envvar.set) {
+ envvar.value = envvar.set;
+ }
+ }
+
+ StackService.createStackFromGitRepository(stackName, template.Repository.url, template.Repository.stackfile, template.Env)
+ .then(function success() {
+ Notifications.success('Stack successfully created');
+ })
+ .catch(function error(err) {
+ Notifications.warning('Deployment error', err.err.data.err);
+ })
+ .then(function success(data) {
+ return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []);
+ })
+ .then(function success() {
+ $state.go('stacks', {}, {reload: true});
+ })
+ .finally(function final() {
+ $('#createResourceSpinner').hide();
+ });
+ }
+
+ $scope.createTemplate = function() {
+ $('#createResourceSpinner').show();
+
+ var userDetails = Authentication.getUserDetails();
+ var userId = userDetails.ID;
+ var accessControlData = $scope.formValues.AccessControlData;
+ var isAdmin = userDetails.role === 1 ? true : false;
+
+ if (!validateForm(accessControlData, isAdmin)) {
+ $('#createResourceSpinner').hide();
+ return;
+ }
+
+ var template = $scope.state.selectedTemplate;
+ var templatesKey = $scope.templatesKey;
+
+ if (template.Type === 'stack') {
+ createStackFromTemplate(template, userId, accessControlData);
+ } else {
+ createContainerFromTemplate(template, userId, accessControlData);
+ }
};
$scope.unselectTemplate = function() {
@@ -152,11 +192,22 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
return containerMapping;
}
- function initTemplates() {
- var templatesKey = $transition$.params().key;
- var provider = $scope.applicationState.endpoint.mode.provider;
- var apiVersion = $scope.applicationState.endpoint.apiVersion;
+ $scope.updateCategories = function(templates, type) {
+ $scope.state.filters.Categories = '!';
+ updateCategories(templates, type);
+ };
+ function updateCategories(templates, type) {
+ var availableCategories = [];
+ angular.forEach(templates, function(template) {
+ if (template.Type === type) {
+ availableCategories = availableCategories.concat(template.Categories);
+ }
+ });
+ $scope.availableCategories = _.sortBy(_.uniq(availableCategories));
+ }
+
+ function initTemplates(templatesKey, type, provider, apiVersion) {
$q.all({
templates: TemplateService.getTemplates(templatesKey),
containers: ContainerService.containers(0),
@@ -169,12 +220,9 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
settings: SettingsService.publicSettings()
})
.then(function success(data) {
- $scope.templates = data.templates;
- var availableCategories = [];
- angular.forEach($scope.templates, function(template) {
- availableCategories = availableCategories.concat(template.Categories);
- });
- $scope.availableCategories = _.sortBy(_.uniq(availableCategories));
+ var templates = data.templates;
+ updateCategories(templates, type);
+ $scope.templates = templates;
$scope.runningContainers = data.containers;
$scope.availableVolumes = data.volumes.Volumes;
var networks = data.networks;
@@ -182,17 +230,33 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
$scope.globalNetworkCount = networks.length;
var settings = data.settings;
$scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
- var userDetails = Authentication.getUserDetails();
- $scope.isAdmin = userDetails.role === 1 ? true : false;
})
.catch(function error(err) {
$scope.templates = [];
Notifications.error('Failure', err, 'An error occured during apps initialization.');
})
.finally(function final(){
- $('#loadTemplatesSpinner').hide();
+ $('#loadingViewSpinner').hide();
});
}
- initTemplates();
+ function initView() {
+ var templatesKey = $transition$.params().key;
+ $scope.templatesKey = templatesKey;
+
+ var userDetails = Authentication.getUserDetails();
+ $scope.isAdmin = userDetails.role === 1 ? true : false;
+
+ var endpointMode = $scope.applicationState.endpoint.mode;
+ var apiVersion = $scope.applicationState.endpoint.apiVersion;
+
+ if (endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' && apiVersion >= 1.25) {
+ $scope.state.filters.Type = 'stack';
+ $scope.state.showDeploymentSelector = true;
+ }
+
+ initTemplates(templatesKey, $scope.state.filters.Type, endpointMode.provider, apiVersion);
+ }
+
+ initView();
}]);
diff --git a/app/models/api/stackTemplate.js b/app/models/api/stackTemplate.js
new file mode 100644
index 000000000..728d98421
--- /dev/null
+++ b/app/models/api/stackTemplate.js
@@ -0,0 +1,11 @@
+function StackTemplateViewModel(data) {
+ this.Type = data.type;
+ this.Title = data.title;
+ this.Description = data.description;
+ this.Note = data.note;
+ this.Categories = data.categories ? data.categories : [];
+ this.Platform = data.platform ? data.platform : 'undefined';
+ this.Logo = data.logo;
+ this.Repository = data.repository;
+ this.Env = data.env ? data.env : [];
+}
diff --git a/app/models/api/template.js b/app/models/api/template.js
index e6499ee33..76a77d961 100644
--- a/app/models/api/template.js
+++ b/app/models/api/template.js
@@ -1,4 +1,5 @@
function TemplateViewModel(data) {
+ this.Type = data.type;
this.Title = data.title;
this.Description = data.description;
this.Note = data.note;
diff --git a/app/services/templateService.js b/app/services/templateService.js
index 57fe4b9b2..89694da59 100644
--- a/app/services/templateService.js
+++ b/app/services/templateService.js
@@ -9,7 +9,9 @@ angular.module('portainer.services')
.then(function success(data) {
var templates = data.map(function (tpl, idx) {
var template;
- if (key === 'linuxserver.io') {
+ if (tpl.type === 'stack') {
+ template = new StackTemplateViewModel(tpl);
+ } else if (tpl.type === 'container' && key === 'linuxserver.io') {
template = new TemplateLSIOViewModel(tpl);
} else {
template = new TemplateViewModel(tpl);
diff --git a/assets/css/app.css b/assets/css/app.css
index 048acf83c..3f6979afb 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -287,6 +287,10 @@ ul.sidebar .sidebar-title {
height: auto;
}
+ul.sidebar .sidebar-list a {
+ font-size: 14px;
+}
+
ul.sidebar .sidebar-list a.active {
color: #fff;
text-indent: 22px;