mirror of
https://github.com/portainer/portainer.git
synced 2025-08-08 15:25:22 +02:00
feat(stacks): support compose v2.0 stack (#1963)
This commit is contained in:
parent
ef15cd30eb
commit
e3d564325b
174 changed files with 7898 additions and 5849 deletions
|
@ -15,15 +15,7 @@
|
|||
order-by="Status" show-text-filter="true"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
show-host-column="applicationState.endpoint.mode.agentProxy"
|
||||
public-url="state.publicURL"
|
||||
container-name-truncate-size="truncate_size"
|
||||
start-action="startAction"
|
||||
stop-action="stopAction"
|
||||
restart-action="restartAction"
|
||||
kill-action="killAction"
|
||||
pause-action="pauseAction"
|
||||
resume-action="resumeAction"
|
||||
remove-action="confirmRemoveAction"
|
||||
show-add-action="true"
|
||||
></containers-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,135 +1,11 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('ContainersController', ['$q', '$scope', '$state', '$filter', '$transition$', 'ContainerService', 'SystemService', 'Notifications', 'ModalService', 'EndpointProvider', 'HttpRequestHelper',
|
||||
function ($q, $scope, $state, $filter, $transition$, ContainerService, SystemService, Notifications, ModalService, EndpointProvider, HttpRequestHelper) {
|
||||
$scope.state = {
|
||||
publicURL: EndpointProvider.endpointPublicURL()
|
||||
};
|
||||
|
||||
$scope.startAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully started';
|
||||
var errorMessage = 'Unable to start container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.startContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.stopAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully stopped';
|
||||
var errorMessage = 'Unable to stop container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.stopContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.restartAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully restarted';
|
||||
var errorMessage = 'Unable to restart container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.restartContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.killAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully killed';
|
||||
var errorMessage = 'Unable to kill container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.killContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.pauseAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully paused';
|
||||
var errorMessage = 'Unable to pause container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.pauseContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.resumeAction = function(selectedItems) {
|
||||
var successMessage = 'Container successfully resumed';
|
||||
var errorMessage = 'Unable to resume container';
|
||||
executeActionOnContainerList(selectedItems, ContainerService.resumeContainer, successMessage, errorMessage);
|
||||
};
|
||||
|
||||
$scope.confirmRemoveAction = function(selectedItems) {
|
||||
var isOneContainerRunning = false;
|
||||
for (var i = 0; i < selectedItems.length; i++) {
|
||||
var container = selectedItems[i];
|
||||
if (container.State === 'running') {
|
||||
isOneContainerRunning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var title = 'You are about to remove one or more container.';
|
||||
if (isOneContainerRunning) {
|
||||
title = 'You are about to remove one or more running container.';
|
||||
}
|
||||
|
||||
ModalService.confirmContainerDeletion(title, function (result) {
|
||||
if(!result) { return; }
|
||||
var cleanVolumes = false;
|
||||
if (result[0]) {
|
||||
cleanVolumes = true;
|
||||
}
|
||||
removeAction(selectedItems, cleanVolumes);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function executeActionOnContainerList(containers, action, successMessage, errorMessage) {
|
||||
var actionCount = containers.length;
|
||||
angular.forEach(containers, function (container) {
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName);
|
||||
action(container.Id)
|
||||
.then(function success() {
|
||||
Notifications.success(successMessage, container.Names[0]);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, errorMessage);
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.transitionTo($state.current, { selectedContainers: containers }, { reload: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeAction(containers, cleanVolumes) {
|
||||
var actionCount = containers.length;
|
||||
angular.forEach(containers, function (container) {
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(container.NodeName);
|
||||
ContainerService.remove(container, cleanVolumes)
|
||||
.then(function success() {
|
||||
Notifications.success('Container successfully removed', container.Names[0]);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove container');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assignContainers(containers) {
|
||||
var previouslySelectedContainers = $transition$.params().selectedContainers || [];
|
||||
$scope.containers = containers.map(function (container) {
|
||||
container.Status = $filter('containerstatus')(container.Status);
|
||||
|
||||
var previousContainer = _.find(previouslySelectedContainers, function(item) {
|
||||
return item.Id === container.Id;
|
||||
});
|
||||
|
||||
if (previousContainer && previousContainer.Checked) {
|
||||
container.Checked = true;
|
||||
}
|
||||
|
||||
return container;
|
||||
});
|
||||
}
|
||||
.controller('ContainersController', ['$scope', 'ContainerService', 'Notifications',
|
||||
function ($scope, ContainerService, Notifications) {
|
||||
|
||||
function initView() {
|
||||
var provider = $scope.applicationState.endpoint.mode.provider;
|
||||
|
||||
ContainerService.containers(1)
|
||||
.then(function success(data) {
|
||||
assignContainers(data);
|
||||
$scope.containers = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve containers');
|
||||
|
|
|
@ -29,9 +29,6 @@
|
|||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span></span>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
|
@ -77,8 +74,8 @@
|
|||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-6" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
|
||||
<a ui-sref="docker.stacks">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<a ui-sref="portainer.stacks">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="widget-icon blue pull-left">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('DashboardController', ['$scope', '$q', 'Container', 'ContainerHelper', 'Image', 'Network', 'Volume', 'SystemService', 'ServiceService', 'StackService', 'Notifications',
|
||||
function ($scope, $q, Container, ContainerHelper, Image, Network, Volume, SystemService, ServiceService, StackService, Notifications) {
|
||||
.controller('DashboardController', ['$scope', '$q', 'Container', 'ContainerHelper', 'Image', 'Network', 'Volume', 'SystemService', 'ServiceService', 'StackService', 'Notifications', 'EndpointProvider',
|
||||
function ($scope, $q, Container, ContainerHelper, Image, Network, Volume, SystemService, ServiceService, StackService, Notifications, EndpointProvider) {
|
||||
|
||||
$scope.containerData = {
|
||||
total: 0
|
||||
|
@ -65,8 +65,8 @@ function ($scope, $q, Container, ContainerHelper, Image, Network, Volume, System
|
|||
}
|
||||
|
||||
function initView() {
|
||||
var endpointProvider = $scope.applicationState.endpoint.mode.provider;
|
||||
var endpointRole = $scope.applicationState.endpoint.mode.role;
|
||||
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||
var endpointId = EndpointProvider.endpointID();
|
||||
|
||||
$q.all([
|
||||
Container.query({all: 1}).$promise,
|
||||
|
@ -74,8 +74,12 @@ function ($scope, $q, Container, ContainerHelper, Image, Network, Volume, System
|
|||
Volume.query({}).$promise,
|
||||
Network.query({}).$promise,
|
||||
SystemService.info(),
|
||||
endpointProvider === 'DOCKER_SWARM_MODE' && endpointRole === 'MANAGER' ? ServiceService.services() : [],
|
||||
endpointProvider === 'DOCKER_SWARM_MODE' && endpointRole === 'MANAGER' ? StackService.stacks(true) : []
|
||||
endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER' ? ServiceService.services() : [],
|
||||
StackService.stacks(
|
||||
true,
|
||||
endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER',
|
||||
endpointId
|
||||
)
|
||||
]).then(function (d) {
|
||||
prepareContainerData(d[0]);
|
||||
prepareImageData(d[1]);
|
||||
|
|
|
@ -25,7 +25,7 @@ function ($scope, $state, NetworkService, Notifications, HttpRequestHelper) {
|
|||
};
|
||||
|
||||
function initView() {
|
||||
NetworkService.networks(true, true, true, true)
|
||||
NetworkService.networks(true, true, true)
|
||||
.then(function success(data) {
|
||||
$scope.networks = data;
|
||||
})
|
||||
|
|
|
@ -496,7 +496,7 @@ function ($q, $scope, $state, $timeout, Service, ServiceHelper, ConfigService, C
|
|||
|
||||
$q.all({
|
||||
volumes: VolumeService.volumes(),
|
||||
networks: NetworkService.networks(true, true, false, false),
|
||||
networks: NetworkService.networks(true, true, false),
|
||||
secrets: apiVersion >= 1.25 ? SecretService.secrets() : [],
|
||||
configs: apiVersion >= 1.30 ? ConfigService.configs() : [],
|
||||
nodes: NodeService.nodes(),
|
||||
|
|
|
@ -7,18 +7,19 @@
|
|||
<rd-header-content>Services</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="row" ng-if="services">
|
||||
<div class="col-sm-12">
|
||||
<services-datatable
|
||||
title-text="Services" title-icon="fa-list-alt"
|
||||
dataset="services" table-key="services"
|
||||
order-by="Name" show-text-filter="true"
|
||||
nodes="nodes"
|
||||
agent-proxy="applicationState.endpoint.mode.agentProxy"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
remove-action="removeAction"
|
||||
scale-action="scaleAction"
|
||||
force-update-action="forceUpdateAction"
|
||||
public-url="state.publicURL"
|
||||
show-force-update-button="applicationState.endpoint.apiVersion >= 1.25"
|
||||
show-update-action="applicationState.endpoint.apiVersion >= 1.25"
|
||||
show-task-logs-button="applicationState.endpoint.apiVersion >= 1.30"
|
||||
show-add-action="true"
|
||||
show-stack-column="true"
|
||||
></services-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,109 +1,36 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('ServicesController', ['$q', '$scope', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Task', 'Node', 'ModalService', 'EndpointProvider',
|
||||
function ($q, $scope, $state, Service, ServiceService, ServiceHelper, Notifications, Task, Node, ModalService, EndpointProvider) {
|
||||
|
||||
$scope.state = {
|
||||
publicURL: EndpointProvider.endpointPublicURL()
|
||||
};
|
||||
|
||||
$scope.scaleAction = function scaleService(service) {
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
config.Mode.Replicated.Replicas = service.Replicas;
|
||||
ServiceService.update(service, config)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully scaled', 'New replica count: ' + service.Replicas);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to scale service');
|
||||
service.Scale = false;
|
||||
service.Replicas = service.ReplicaCount;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.forceUpdateAction = function(selectedItems) {
|
||||
ModalService.confirmServiceForceUpdate(
|
||||
'Do you want to force update of selected service(s)? All the tasks associated to the selected service(s) will be recreated.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
forceUpdateServices(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function forceUpdateServices(services) {
|
||||
var actionCount = services.length;
|
||||
angular.forEach(services, function (service) {
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
// As explained in https://github.com/docker/swarmkit/issues/2364 ForceUpdate can accept a random
|
||||
// value or an increment of the counter value to force an update.
|
||||
config.TaskTemplate.ForceUpdate++;
|
||||
ServiceService.update(service, config)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully updated', service.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to force update service', service.Name);
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.removeAction = function(selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
removeServices(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function removeServices(services) {
|
||||
var actionCount = services.length;
|
||||
angular.forEach(services, function (service) {
|
||||
ServiceService.remove(service)
|
||||
.then(function success() {
|
||||
Notifications.success('Service successfully removed', service.Name);
|
||||
var index = $scope.services.indexOf(service);
|
||||
$scope.services.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove service');
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
.controller('ServicesController', ['$q', '$scope', 'ServiceService', 'ServiceHelper', 'Notifications', 'TaskService', 'TaskHelper', 'NodeService', 'ContainerService',
|
||||
function ($q, $scope, ServiceService, ServiceHelper, Notifications, TaskService, TaskHelper, NodeService, ContainerService) {
|
||||
|
||||
function initView() {
|
||||
var agentProxy = $scope.applicationState.endpoint.mode.agentProxy;
|
||||
|
||||
$q.all({
|
||||
services: Service.query({}).$promise,
|
||||
tasks: Task.query({filters: {'desired-state': ['running','accepted']}}).$promise,
|
||||
nodes: Node.query({}).$promise
|
||||
services: ServiceService.services(),
|
||||
tasks: TaskService.tasks(),
|
||||
containers: agentProxy ? ContainerService.containers(1) : [],
|
||||
nodes: NodeService.nodes()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.services = data.services.map(function (service) {
|
||||
var runningTasks = data.tasks.filter(function (task) {
|
||||
return task.ServiceID === service.ID && task.Status.State === 'running';
|
||||
});
|
||||
var allTasks = data.tasks.filter(function (task) {
|
||||
return task.ServiceID === service.ID;
|
||||
});
|
||||
var taskNodes = data.nodes.filter(function (node) {
|
||||
return node.Spec.Availability === 'active' && node.Status.State === 'ready';
|
||||
});
|
||||
return new ServiceViewModel(service, runningTasks, allTasks, taskNodes);
|
||||
});
|
||||
var services = data.services;
|
||||
var tasks = data.tasks;
|
||||
|
||||
if (agentProxy) {
|
||||
var containers = data.containers;
|
||||
for (var j = 0; j < tasks.length; j++) {
|
||||
var task = tasks[j];
|
||||
TaskHelper.associateContainerToTask(task, containers);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
var service = services[i];
|
||||
ServiceHelper.associateTasksToService(service, tasks);
|
||||
}
|
||||
|
||||
$scope.nodes = data.nodes;
|
||||
$scope.tasks = tasks;
|
||||
$scope.services = services;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.services = [];
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('CreateStackController', ['$scope', '$state', 'StackService', 'Authentication', 'Notifications', 'FormValidator', 'ResourceControlService', 'FormHelper',
|
||||
function ($scope, $state, StackService, Authentication, Notifications, FormValidator, ResourceControlService, FormHelper) {
|
||||
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
StackFileContent: '',
|
||||
StackFile: null,
|
||||
RepositoryURL: '',
|
||||
RepositoryAuthentication: false,
|
||||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
Env: [],
|
||||
ComposeFilePathInRepository: 'docker-compose.yml',
|
||||
AccessControlData: new AccessControlFormData()
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
Method: 'editor',
|
||||
formValidationError: '',
|
||||
actionInProgress: false
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function() {
|
||||
$scope.formValues.Env.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function(index) {
|
||||
$scope.formValues.Env.splice(index, 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 createStack(name, method) {
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.formValues.Env);
|
||||
|
||||
if (method === 'editor') {
|
||||
var stackFileContent = $scope.formValues.StackFileContent;
|
||||
return StackService.createStackFromFileContent(name, stackFileContent, env);
|
||||
} else if (method === 'upload') {
|
||||
var stackFile = $scope.formValues.StackFile;
|
||||
return StackService.createStackFromFileUpload(name, stackFile, env);
|
||||
} else if (method === 'repository') {
|
||||
var repositoryOptions = {
|
||||
RepositoryURL: $scope.formValues.RepositoryURL,
|
||||
ComposeFilePathInRepository: $scope.formValues.ComposeFilePathInRepository,
|
||||
RepositoryAuthentication: $scope.formValues.RepositoryAuthentication,
|
||||
RepositoryUsername: $scope.formValues.RepositoryUsername,
|
||||
RepositoryPassword: $scope.formValues.RepositoryPassword
|
||||
};
|
||||
return StackService.createStackFromGitRepository(name, repositoryOptions, env);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.deployStack = function () {
|
||||
var name = $scope.formValues.Name;
|
||||
var method = $scope.state.Method;
|
||||
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1;
|
||||
var userId = userDetails.ID;
|
||||
|
||||
if (method === 'editor' && $scope.formValues.StackFileContent === '') {
|
||||
$scope.state.formValidationError = 'Stack file content must not be empty';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
createStack(name, method)
|
||||
.then(function success(data) {
|
||||
return ResourceControlService.applyResourceControl('stack', name, userId, accessControlData, []);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully deployed');
|
||||
$state.go('docker.stacks');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.warning('Deployment error', err.err.data.err);
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editorUpdate = function(cm) {
|
||||
$scope.formValues.StackFileContent = cm.getValue();
|
||||
};
|
||||
}]);
|
|
@ -1,212 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Create stack"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="docker.stacks">Stacks</a> > Add stack
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="stack_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="formValues.Name" id="stack_name" placeholder="e.g. myStack" auto-focus>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
This stack will be deployed using the equivalent of the <code>docker stack deploy</code> command.
|
||||
</span>
|
||||
</div>
|
||||
<!-- build-method -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Build method
|
||||
</div>
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="method_editor" ng-model="state.Method" value="editor">
|
||||
<label for="method_editor">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Web editor
|
||||
</div>
|
||||
<p>Use our Web editor</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_upload" ng-model="state.Method" value="upload">
|
||||
<label for="method_upload">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-upload" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Upload
|
||||
</div>
|
||||
<p>Upload from your computer</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="method_repository" ng-model="state.Method" value="repository">
|
||||
<label for="method_repository">
|
||||
<div class="boxselector_header">
|
||||
<i class="fab fa-git" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Repository
|
||||
</div>
|
||||
<p>Use a git repository</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !build-method -->
|
||||
<!-- web-editor -->
|
||||
<div ng-show="state.Method === 'editor'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Web editor
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can get more information about Compose file format in the <a href="https://docs.docker.com/compose/compose-file/" target="_blank">official documentation</a>.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<code-editor
|
||||
identifier="stack-creation-editor"
|
||||
placeholder="# Define or paste the content of your docker-compose file here"
|
||||
yml="true"
|
||||
on-change="editorUpdate"
|
||||
></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !web-editor -->
|
||||
<!-- upload -->
|
||||
<div ng-show="state.Method === 'upload'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Upload
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can upload a Compose file from your computer.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.StackFile">Select file</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ formValues.StackFile.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!formValues.StackFile" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !upload -->
|
||||
<!-- repository -->
|
||||
<div ng-show="state.Method === 'repository'">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Git repository
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can use the URL of a git repository.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stack_repository_url" class="col-sm-2 control-label text-left">Repository URL</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" ng-model="formValues.RepositoryURL" id="stack_repository_url" placeholder="https://github.com/portainer/portainer-compose">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
Indicate the path to the Compose file from the root of your repository.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stack_repository_path" class="col-sm-2 control-label text-left">Compose path</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" ng-model="formValues.ComposeFilePathInRepository" id="stack_repository_path" placeholder="docker-compose.yml">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Authentication
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="formValues.RepositoryAuthentication"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="formValues.RepositoryAuthentication">
|
||||
<label for="repository_username" class="col-sm-1 control-label text-left">Username</label>
|
||||
<div class="col-sm-11 col-md-5">
|
||||
<input type="text" class="form-control" ng-model="formValues.RepositoryUsername" name="repository_username" placeholder="myGitUser">
|
||||
</div>
|
||||
<label for="repository_password" class="col-sm-1 margin-sm-top control-label text-left">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-11 col-md-5 margin-sm-top">
|
||||
<input type="password" class="form-control" ng-model="formValues.RepositoryPassword" name="repository_password" placeholder="myPassword">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Environment
|
||||
</div>
|
||||
<!-- environment-variables -->
|
||||
<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()">
|
||||
<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 formValues.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">
|
||||
</div>
|
||||
<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. bar">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
<!-- !repository -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress
|
||||
|| (state.Method === 'upload' && !formValues.StackFile)
|
||||
|| (state.Method === 'repository' && ((!formValues.RepositoryURL || !formValues.ComposeFilePathInRepository) || (formValues.RepositoryAuthentication && (!formValues.RepositoryUsername || !formValues.RepositoryPassword))))
|
||||
|| !formValues.Name" ng-click="deployStack()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress">Deploy the stack</span>
|
||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
||||
</button>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -1,133 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Stack details">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.stacks.stack({id: stack.Id})" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="docker.stacks">Stacks</a> > <a ui-sref="docker.stacks.stack({id: stack.Id})">{{ stack.Name }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<!-- access-control-panel -->
|
||||
<por-access-control-panel
|
||||
ng-if="stack && applicationState.application.authentication"
|
||||
resource-id="stack.Name"
|
||||
resource-control="stack.ResourceControl"
|
||||
resource-type="'stack'">
|
||||
</por-access-control-panel>
|
||||
<!-- !access-control-panel -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<stack-services-datatable
|
||||
title-text="Services" title-icon="fa-list-alt"
|
||||
dataset="services" table-key="stack-services"
|
||||
order-by="Name"
|
||||
nodes="nodes"
|
||||
public-url="state.publicURL"
|
||||
show-text-filter="true"
|
||||
scale-action="scaleAction"
|
||||
></stack-services-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<tasks-datatable
|
||||
title-text="Tasks" title-icon="fa-tasks"
|
||||
dataset="tasks" table-key="stack-tasks"
|
||||
order-by="Updated" reverse-order="true"
|
||||
nodes="nodes"
|
||||
show-text-filter="true"
|
||||
show-slot-column="true"
|
||||
show-logs-button="applicationState.endpoint.apiVersion >= 1.30"
|
||||
agent-proxy="applicationState.endpoint.mode.agentProxy"
|
||||
></tasks-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="stackFileContent">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-pencil-alt" title-text="Stack editor"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
You can get more information about Compose file format in the <a href="https://docs.docker.com/compose/compose-file/" target="_blank">official documentation</a>.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<code-editor
|
||||
identifier="stack-editor"
|
||||
placeholder="# Define or paste the content of your docker-compose file here"
|
||||
yml="true"
|
||||
on-change="editorUpdate"
|
||||
value="stackFileContent"
|
||||
></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Environment
|
||||
</div>
|
||||
<!-- environment-variables -->
|
||||
<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()">
|
||||
<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 stack.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">
|
||||
</div>
|
||||
<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. bar">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
<!-- options -->
|
||||
<div class="col-sm-12 form-section-title" ng-if="applicationState.endpoint.apiVersion >= 1.27">
|
||||
Options
|
||||
</div>
|
||||
<div class="form-group" ng-if="applicationState.endpoint.apiVersion >= 1.27">
|
||||
<div class="col-sm-12">
|
||||
<label for="prune" class="control-label text-left">
|
||||
Prune services
|
||||
<portainer-tooltip position="bottom" message="Prune services that are no longer referenced."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input name="prune" type="checkbox" ng-model="formValues.Prune"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !options -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-disabled="state.actionInProgress" ng-click="deployStack()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress">Update the stack</span>
|
||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -1,111 +0,0 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('StackController', ['$q', '$scope', '$state', '$transition$', 'StackService', 'NodeService', 'ServiceService', 'TaskService', 'ContainerService', 'ServiceHelper', 'TaskHelper', 'Notifications', 'FormHelper', 'EndpointProvider',
|
||||
function ($q, $scope, $state, $transition$, StackService, NodeService, ServiceService, TaskService, ContainerService, ServiceHelper, TaskHelper, Notifications, FormHelper, EndpointProvider) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
publicURL: EndpointProvider.endpointPublicURL()
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
Prune: false
|
||||
};
|
||||
|
||||
$scope.deployStack = function () {
|
||||
var stackFile = $scope.stackFileContent;
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.stack.Env);
|
||||
var prune = $scope.formValues.Prune;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
StackService.updateStack($scope.stack.Id, stackFile, env, prune)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Stack successfully deployed');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create stack');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function() {
|
||||
$scope.stack.Env.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeEnvironmentVariable = function(index) {
|
||||
$scope.stack.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
function initView() {
|
||||
var stackId = $transition$.params().id;
|
||||
var apiVersion = $scope.applicationState.endpoint.apiVersion;
|
||||
var agentProxy = $scope.applicationState.endpoint.mode.agentProxy;
|
||||
|
||||
StackService.stack(stackId)
|
||||
.then(function success(data) {
|
||||
var stack = data;
|
||||
$scope.stack = stack;
|
||||
|
||||
var serviceFilters = {
|
||||
label: ['com.docker.stack.namespace=' + stack.Name]
|
||||
};
|
||||
|
||||
return $q.all({
|
||||
stackFile: StackService.getStackFile(stackId),
|
||||
services: ServiceService.services(serviceFilters),
|
||||
tasks: TaskService.tasks(serviceFilters),
|
||||
containers: agentProxy ? ContainerService.containers() : [],
|
||||
nodes: NodeService.nodes()
|
||||
});
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.stackFileContent = data.stackFile;
|
||||
$scope.nodes = data.nodes;
|
||||
|
||||
var services = data.services;
|
||||
var tasks = data.tasks;
|
||||
|
||||
if (agentProxy) {
|
||||
var containers = data.containers;
|
||||
for (var j = 0; j < tasks.length; j++) {
|
||||
var task = tasks[j];
|
||||
TaskHelper.associateContainerToTask(task, containers);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
var service = services[i];
|
||||
ServiceHelper.associateTasksToService(service, tasks);
|
||||
}
|
||||
|
||||
$scope.tasks = tasks;
|
||||
$scope.services = services;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve tasks details');
|
||||
});
|
||||
}
|
||||
|
||||
$scope.editorUpdate = function(cm) {
|
||||
$scope.stackFileContent = cm.getValue();
|
||||
};
|
||||
|
||||
$scope.scaleAction = function scaleService(service) {
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
config.Mode.Replicated.Replicas = service.Replicas;
|
||||
ServiceService.update(service, config)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully scaled', 'New replica count: ' + service.Replicas);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to scale service');
|
||||
service.Scale = false;
|
||||
service.Replicas = service.ReplicaCount;
|
||||
});
|
||||
};
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -1,51 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Stacks list">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.stacks" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Stacks</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="state.displayInformationPanel">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-info-circle" title-text="Information"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
Stacks marked with the <i class="fa fa-exclamation-circle orange-icon" aria-hidden="true"></i> icon are external stacks that were created outside of Portainer. You'll not be able to execute any actions against these stacks.
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Filters
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label text-left">
|
||||
Display external stacks
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" ng-model="state.displayExternalStacks"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<stacks-datatable
|
||||
title-text="Stacks" title-icon="fa-th-list"
|
||||
dataset="stacks" table-key="stacks"
|
||||
order-by="Name" show-text-filter="true"
|
||||
remove-action="removeAction"
|
||||
show-ownership-column="applicationState.application.authentication"
|
||||
display-external-stacks="state.displayExternalStacks"
|
||||
></stacks-datatable>
|
||||
</div>
|
||||
</div>
|
|
@ -1,60 +0,0 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('StacksController', ['$scope', '$state', 'Notifications', 'StackService', 'ModalService',
|
||||
function ($scope, $state, Notifications, StackService, ModalService) {
|
||||
$scope.state = {
|
||||
displayInformationPanel: false,
|
||||
displayExternalStacks: true
|
||||
};
|
||||
|
||||
$scope.removeAction = function(selectedItems) {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to remove the selected stack(s)? Associated services will be removed as well.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
deleteSelectedStacks(selectedItems);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function deleteSelectedStacks(stacks) {
|
||||
var actionCount = stacks.length;
|
||||
angular.forEach(stacks, function (stack) {
|
||||
StackService.remove(stack)
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully removed', stack.Name);
|
||||
var index = $scope.stacks.indexOf(stack);
|
||||
$scope.stacks.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove stack ' + stack.Name);
|
||||
})
|
||||
.finally(function final() {
|
||||
--actionCount;
|
||||
if (actionCount === 0) {
|
||||
$state.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
StackService.stacks(true)
|
||||
.then(function success(data) {
|
||||
var stacks = data;
|
||||
for (var i = 0; i < stacks.length; i++) {
|
||||
var stack = stacks[i];
|
||||
if (stack.External) {
|
||||
$scope.state.displayInformationPanel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$scope.stacks = stacks;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.stacks = [];
|
||||
Notifications.error('Failure', err, 'Unable to retrieve stacks');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -44,7 +44,7 @@
|
|||
<td>Container ID</td>
|
||||
<td>{{ task.Status.ContainerStatus.ContainerID }}</td>
|
||||
</tr>
|
||||
<tr ng-if="applicationState.endpoint.apiVersion >= 1.30" >
|
||||
<tr ng-if="applicationState.endpoint.apiVersion >= 1.30 && task.Status.State|taskhaslogs">
|
||||
<td colspan="2"><a class="btn btn-primary btn-sm" type="button" ui-sref="docker.tasks.task.logs({id: task.Id})"><i class="fa fa-file-alt space-right" aria-hidden="true"></i>Task logs</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.docker')
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'PaginationService', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService',
|
||||
function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, PaginationService, ResourceControlService, Authentication, FormValidator, SettingsService, StackService) {
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$transition$', '$anchorScroll', '$filter', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'PaginationService', 'ResourceControlService', 'Authentication', 'FormValidator', 'SettingsService', 'StackService', 'EndpointProvider',
|
||||
function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, PaginationService, ResourceControlService, Authentication, FormValidator, SettingsService, StackService, EndpointProvider) {
|
||||
$scope.state = {
|
||||
selectedTemplate: null,
|
||||
showAdvancedOptions: false,
|
||||
|
@ -113,13 +113,14 @@ function ($scope, $q, $state, $transition$, $anchorScroll, $filter, ContainerSer
|
|||
ComposeFilePathInRepository: template.Repository.stackfile
|
||||
};
|
||||
|
||||
StackService.createStackFromGitRepository(stackName, repositoryOptions, template.Env)
|
||||
var endpointId = EndpointProvider.endpointID();
|
||||
StackService.createSwarmStackFromGitRepository(stackName, repositoryOptions, template.Env, endpointId)
|
||||
.then(function success(data) {
|
||||
return ResourceControlService.applyResourceControl('stack', stackName, userId, accessControlData, []);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully deployed');
|
||||
$state.go('docker.stacks');
|
||||
$state.go('portainer.stacks');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.warning('Deployment error', err.err.data.err);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue