mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
* feat(stack): add the ability for an administrator user to manage orphaned stacks (#4397) * feat(stack): apply small font size to the information text of associate (#4397) Co-authored-by: Simon Meng <simon.meng@portainer.io>
This commit is contained in:
parent
eae2f5c9fc
commit
26ead28d7b
17 changed files with 428 additions and 117 deletions
|
@ -456,7 +456,7 @@ angular.module('portainer.docker', ['portainer.app']).config([
|
|||
|
||||
var stack = {
|
||||
name: 'docker.stacks.stack',
|
||||
url: '/:name?id&type&external',
|
||||
url: '/:name?id&type®ular&external&orphaned&orphanedRunning',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: '~Portainer/views/stacks/edit/stack.html',
|
||||
|
|
|
@ -8,5 +8,6 @@ angular.module('portainer.app').component('porAccessControlForm', {
|
|||
// Optional. An existing resource control object that will be used to set
|
||||
// the default values of the component.
|
||||
resourceControl: '<',
|
||||
hideTitle: '<',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
<div ng-if="!$ctrl.hideTitle" class="col-sm-12 form-section-title">
|
||||
Access control
|
||||
</div>
|
||||
<!-- access-control-switch -->
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
</div>
|
||||
<div class="menuContent">
|
||||
<div>
|
||||
<div class="md-checkbox" ng-if="$ctrl.isAdmin">
|
||||
<input id="setting_all_orphaned_stacks" type="checkbox" ng-model="$ctrl.settings.allOrphanedStacks" ng-change="$ctrl.onSettingsAllOrphanedStacksChange()" />
|
||||
<label for="setting_all_orphaned_stacks">Show all orphaned stacks</label>
|
||||
</div>
|
||||
<div class="md-checkbox">
|
||||
<input id="setting_auto_refresh" type="checkbox" ng-model="$ctrl.settings.repeater.autoRefresh" ng-change="$ctrl.onSettingsRepeaterChange()" />
|
||||
<label for="setting_auto_refresh">Auto refresh</label>
|
||||
|
@ -151,12 +155,26 @@
|
|||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="!$ctrl.allowSelection(item)" />
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ng-if="!$ctrl.offlineMode" ui-sref="docker.stacks.stack({ name: item.Name, id: item.Id, type: item.Type, external: item.External })">{{ item.Name }}</a>
|
||||
<a
|
||||
ng-if="!$ctrl.offlineMode"
|
||||
ui-sref="docker.stacks.stack({ name: item.Name, id: item.Id, type: item.Type, regular: item.Regular, external: item.External, orphaned: item.Orphaned, orphanedRunning: item.OrphanedRunning })"
|
||||
>{{ item.Name }}</a
|
||||
>
|
||||
<span ng-if="$ctrl.offlineMode">{{ item.Name }}</span>
|
||||
<span ng-if="item.Status == 2" style="margin-left: 10px;" class="label label-warning image-tag space-left">Inactive</span>
|
||||
<span ng-if="item.Regular && item.Status == 2" style="margin-left: 10px;" class="label label-warning image-tag space-left">Inactive</span>
|
||||
</td>
|
||||
<td>{{ item.Type === 1 ? 'Swarm' : 'Compose' }}</td>
|
||||
<td>
|
||||
<span
|
||||
ng-if="item.Orphaned"
|
||||
class="interactive"
|
||||
tooltip-append-to-body="true"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-class="portainer-tooltip"
|
||||
uib-tooltip="This stack was created inside an endpoint that is no longer registered inside Portainer."
|
||||
>
|
||||
Orphaned <i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
</span>
|
||||
<span
|
||||
ng-if="item.External"
|
||||
class="interactive"
|
||||
|
@ -167,7 +185,7 @@
|
|||
>
|
||||
Limited <i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
</span>
|
||||
<span ng-if="!item.External">Total</span>
|
||||
<span ng-if="item.Regular">Total</span>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="item.CreationDate">{{ item.CreationDate | getisodatefromtimestamp }} {{ item.CreatedBy ? 'by ' + item.CreatedBy : '' }}</span>
|
||||
|
|
|
@ -47,7 +47,11 @@ angular.module('portainer.app').controller('StacksDatatableController', [
|
|||
this.applyFilters = applyFilters.bind(this);
|
||||
function applyFilters(stack) {
|
||||
const { showActiveStacks, showUnactiveStacks } = this.filters.state;
|
||||
return (stack.Status === 1 && showActiveStacks) || (stack.Status === 2 && showUnactiveStacks) || stack.External;
|
||||
if (stack.Orphaned) {
|
||||
return stack.OrphanedRunning || this.settings.allOrphanedStacks;
|
||||
} else {
|
||||
return (stack.Status === 1 && showActiveStacks) || (stack.Status === 2 && showUnactiveStacks) || stack.External;
|
||||
}
|
||||
}
|
||||
|
||||
this.onFilterChange = onFilterChange.bind(this);
|
||||
|
@ -57,6 +61,10 @@ angular.module('portainer.app').controller('StacksDatatableController', [
|
|||
DatatableService.setDataTableFilters(this.tableKey, this.filters);
|
||||
}
|
||||
|
||||
this.onSettingsAllOrphanedStacksChange = function () {
|
||||
DatatableService.setDataTableSettings(this.tableKey, this.settings);
|
||||
};
|
||||
|
||||
this.$onInit = function () {
|
||||
this.isAdmin = Authentication.isAdmin();
|
||||
this.setDefaults();
|
||||
|
@ -87,6 +95,7 @@ angular.module('portainer.app').controller('StacksDatatableController', [
|
|||
if (storedSettings !== null) {
|
||||
this.settings = storedSettings;
|
||||
this.settings.open = false;
|
||||
this.settings.allOrphanedStacks = this.settings.allOrphanedStacks && this.isAdmin;
|
||||
}
|
||||
this.onSettingsRepeaterChange();
|
||||
|
||||
|
|
|
@ -4,25 +4,54 @@ export function StackViewModel(data) {
|
|||
this.Id = data.Id;
|
||||
this.Type = data.Type;
|
||||
this.Name = data.Name;
|
||||
this.Checked = false;
|
||||
this.EndpointId = data.EndpointId;
|
||||
this.SwarmId = data.SwarmId;
|
||||
this.Env = data.Env ? data.Env : [];
|
||||
if (data.ResourceControl && data.ResourceControl.Id !== 0) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
||||
}
|
||||
this.External = false;
|
||||
this.Status = data.Status;
|
||||
this.CreationDate = data.CreationDate;
|
||||
this.CreatedBy = data.CreatedBy;
|
||||
this.UpdateDate = data.UpdateDate;
|
||||
this.UpdatedBy = data.UpdatedBy;
|
||||
|
||||
this.Regular = true;
|
||||
this.External = false;
|
||||
this.Orphaned = false;
|
||||
this.Checked = false;
|
||||
}
|
||||
|
||||
export function ExternalStackViewModel(name, type, creationDate) {
|
||||
this.Name = name;
|
||||
this.Type = type;
|
||||
this.External = true;
|
||||
this.Checked = false;
|
||||
this.CreationDate = creationDate;
|
||||
|
||||
this.Regular = false;
|
||||
this.External = true;
|
||||
this.Orphaned = false;
|
||||
this.Checked = false;
|
||||
}
|
||||
|
||||
export function OrphanedStackViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Type = data.Type;
|
||||
this.Name = data.Name;
|
||||
this.EndpointId = data.EndpointId;
|
||||
this.SwarmId = data.SwarmId;
|
||||
this.Env = data.Env ? data.Env : [];
|
||||
if (data.ResourceControl && data.ResourceControl.Id !== 0) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.ResourceControl);
|
||||
}
|
||||
this.Status = data.Status;
|
||||
this.CreationDate = data.CreationDate;
|
||||
this.CreatedBy = data.CreatedBy;
|
||||
this.UpdateDate = data.UpdateDate;
|
||||
this.UpdatedBy = data.UpdatedBy;
|
||||
|
||||
this.Regular = false;
|
||||
this.External = false;
|
||||
this.Orphaned = true;
|
||||
this.OrphanedRunning = false;
|
||||
this.Checked = false;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ angular.module('portainer.app').factory('Stack', [
|
|||
query: { method: 'GET', isArray: true },
|
||||
create: { method: 'POST', ignoreLoadingBar: true },
|
||||
update: { method: 'PUT', params: { id: '@id' }, ignoreLoadingBar: true },
|
||||
associate: { method: 'PUT', params: { id: '@id', swarmId: '@swarmId', endpointId: '@endpointId', orphanedRunning: '@orphanedRunning', action: 'associate' } },
|
||||
remove: { method: 'DELETE', params: { id: '@id', external: '@external', endpointId: '@endpointId' } },
|
||||
getStackFile: { method: 'GET', params: { id: '@id', action: 'file' } },
|
||||
migrate: { method: 'POST', params: { id: '@id', action: 'migrate', endpointId: '@endpointId' }, ignoreLoadingBar: true },
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash-es';
|
||||
import { StackViewModel } from '../../models/stack';
|
||||
import { StackViewModel, OrphanedStackViewModel } from '../../models/stack';
|
||||
|
||||
angular.module('portainer.app').factory('StackService', [
|
||||
'$q',
|
||||
|
@ -88,15 +88,15 @@ angular.module('portainer.app').factory('StackService', [
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.stacks = function (compose, swarm, endpointId) {
|
||||
service.stacks = function (compose, swarm, endpointId, includeOrphanedStacks = false) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var queries = [];
|
||||
if (compose) {
|
||||
queries.push(service.composeStacks(true, { EndpointID: endpointId }));
|
||||
queries.push(service.composeStacks(endpointId, true, { EndpointID: endpointId, IncludeOrphanedStacks: includeOrphanedStacks }));
|
||||
}
|
||||
if (swarm) {
|
||||
queries.push(service.swarmStacks(true));
|
||||
queries.push(service.swarmStacks(endpointId, true, { IncludeOrphanedStacks: includeOrphanedStacks }));
|
||||
}
|
||||
|
||||
$q.all(queries)
|
||||
|
@ -145,7 +145,22 @@ angular.module('portainer.app').factory('StackService', [
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.composeStacks = function (includeExternalStacks, filters) {
|
||||
service.unionStacks = function (stacks, externalStacks) {
|
||||
stacks.forEach((stack) => {
|
||||
externalStacks.forEach((externalStack) => {
|
||||
if (stack.Orphaned && stack.Name == externalStack.Name) {
|
||||
stack.OrphanedRunning = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
const result = _.unionWith(stacks, externalStacks, function (a, b) {
|
||||
return a.Name === b.Name;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
service.composeStacks = function (endpointId, includeExternalStacks, filters) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$q.all({
|
||||
|
@ -154,14 +169,15 @@ angular.module('portainer.app').factory('StackService', [
|
|||
})
|
||||
.then(function success(data) {
|
||||
var stacks = data.stacks.map(function (item) {
|
||||
item.External = false;
|
||||
return new StackViewModel(item);
|
||||
if (item.EndpointId == endpointId) {
|
||||
return new StackViewModel(item);
|
||||
} else {
|
||||
return new OrphanedStackViewModel(item);
|
||||
}
|
||||
});
|
||||
var externalStacks = data.externalStacks;
|
||||
|
||||
var result = _.unionWith(stacks, externalStacks, function (a, b) {
|
||||
return a.Name === b.Name;
|
||||
});
|
||||
var externalStacks = data.externalStacks;
|
||||
const result = service.unionStacks(stacks, externalStacks);
|
||||
deferred.resolve(result);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -171,13 +187,13 @@ angular.module('portainer.app').factory('StackService', [
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.swarmStacks = function (includeExternalStacks) {
|
||||
service.swarmStacks = function (endpointId, includeExternalStacks, filters = {}) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
SwarmService.swarm()
|
||||
.then(function success(data) {
|
||||
var swarm = data;
|
||||
var filters = { SwarmID: swarm.Id };
|
||||
filters = { SwarmID: swarm.Id, ...filters };
|
||||
|
||||
return $q.all({
|
||||
stacks: Stack.query({ filters: filters }).$promise,
|
||||
|
@ -186,14 +202,15 @@ angular.module('portainer.app').factory('StackService', [
|
|||
})
|
||||
.then(function success(data) {
|
||||
var stacks = data.stacks.map(function (item) {
|
||||
item.External = false;
|
||||
return new StackViewModel(item);
|
||||
if (item.EndpointId == endpointId) {
|
||||
return new StackViewModel(item);
|
||||
} else {
|
||||
return new OrphanedStackViewModel(item);
|
||||
}
|
||||
});
|
||||
var externalStacks = data.externalStacks;
|
||||
|
||||
var result = _.unionWith(stacks, externalStacks, function (a, b) {
|
||||
return a.Name === b.Name;
|
||||
});
|
||||
var externalStacks = data.externalStacks;
|
||||
const result = service.unionStacks(stacks, externalStacks);
|
||||
deferred.resolve(result);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
|
@ -217,6 +234,34 @@ angular.module('portainer.app').factory('StackService', [
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.associate = function (stack, endpointId, orphanedRunning) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
if (stack.Type == 1) {
|
||||
SwarmService.swarm()
|
||||
.then(function success(data) {
|
||||
const swarm = data;
|
||||
return Stack.associate({ id: stack.Id, endpointId: endpointId, swarmId: swarm.Id, orphanedRunning }).$promise;
|
||||
})
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to associate the stack', err: err });
|
||||
});
|
||||
} else {
|
||||
Stack.associate({ id: stack.Id, endpointId: endpointId, orphanedRunning })
|
||||
.$promise.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to associate the stack', err: err });
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.updateStack = function (stack, stackFile, env, prune) {
|
||||
return Stack.update({ endpointId: stack.EndpointId }, { id: stack.Id, StackFileContent: stackFile, Env: env, Prune: prune }).$promise;
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<uib-tab-heading> <i class="fa fa-th-list" aria-hidden="true"></i> Stack </uib-tab-heading>
|
||||
<div style="margin-top: 10px;">
|
||||
<!-- stack-information -->
|
||||
<div ng-if="state.externalStack">
|
||||
<div ng-if="external || orphaned">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Information
|
||||
</div>
|
||||
|
@ -25,7 +25,8 @@
|
|||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
This stack was created outside of Portainer. Control over this stack is limited.
|
||||
<span ng-if="external">This stack was created outside of Portainer. Control over this stack is limited.</span>
|
||||
<span ng-if="orphaned">This stack is orphaned. You can reassociate it with the current environment using the "Associate to this endpoint" feature.</span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -41,7 +42,7 @@
|
|||
|
||||
<button
|
||||
authorization="PortainerStackUpdate"
|
||||
ng-if="!state.externalStack && stack.Status === 2"
|
||||
ng-if="regular && stack.Status === 2"
|
||||
ng-disabled="state.actionInProgress"
|
||||
class="btn btn-xs btn-success"
|
||||
ng-click="startStack()"
|
||||
|
@ -51,7 +52,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
ng-if="!state.externalStack && stack.Status === 1"
|
||||
ng-if="regular && stack.Status === 1"
|
||||
authorization="PortainerStackUpdate"
|
||||
ng-disabled="state.actionInProgress"
|
||||
class="btn btn-xs btn-danger"
|
||||
|
@ -61,13 +62,13 @@
|
|||
Stop this stack
|
||||
</button>
|
||||
|
||||
<button authorization="PortainerStackDelete" class="btn btn-xs btn-danger" ng-click="removeStack()" ng-if="!state.externalStack || stack.Type === 1">
|
||||
<button authorization="PortainerStackDelete" class="btn btn-xs btn-danger" ng-click="removeStack()" ng-if="!external || stack.Type == 1">
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>
|
||||
Delete this stack
|
||||
</button>
|
||||
|
||||
<button
|
||||
ng-if="!state.externalStack && stackFileContent"
|
||||
ng-if="regular && stackFileContent"
|
||||
class="btn btn-primary btn-xs"
|
||||
ui-sref="docker.templates.custom.new({fileContent: stackFileContent, type: stack.Type})"
|
||||
>
|
||||
|
@ -77,8 +78,40 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- !stack-details -->
|
||||
|
||||
<!-- associate -->
|
||||
<div ng-if="orphaned">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Associate to this endpoint
|
||||
</div>
|
||||
<p class="small text-muted">
|
||||
This feature allows you to reassociate this stack to the current endpoint.
|
||||
</p>
|
||||
<form class="form-horizontal">
|
||||
<por-access-control-form form-data="formValues.AccessControlData" hide-title="true"></por-access-control-form>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="state.actionInProgress"
|
||||
ng-click="associateStack()"
|
||||
button-spinner="state.actionInProgress"
|
||||
style="margin-left: -5px;"
|
||||
>
|
||||
<i class="fa fa-sync" aria-hidden="true" style="margin-right: 3px;"></i>
|
||||
<span ng-hide="state.actionInProgress">Associate</span>
|
||||
<span ng-show="state.actionInProgress">Association in progress...</span>
|
||||
</button>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- !associate -->
|
||||
|
||||
<stack-duplication-form
|
||||
ng-if="!state.externalStack && endpoints.length > 0"
|
||||
ng-if="regular && endpoints.length > 0"
|
||||
endpoints="endpoints"
|
||||
groups="groups"
|
||||
current-endpoint-id="currentEndpointId"
|
||||
|
@ -91,7 +124,7 @@
|
|||
</uib-tab>
|
||||
<!-- !tab-info -->
|
||||
<!-- tab-file -->
|
||||
<uib-tab index="1" select="showEditor()" ng-if="!state.externalStack">
|
||||
<uib-tab index="1" select="showEditor()" ng-if="!external">
|
||||
<uib-tab-heading> <i class="fa fa-pencil-alt space-right" aria-hidden="true"></i> Editor </uib-tab-heading>
|
||||
<form class="form-horizontal" ng-if="state.showEditorTab" style="margin-top: 10px;">
|
||||
<div class="form-group">
|
||||
|
@ -108,6 +141,7 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<code-editor
|
||||
read-only="orphaned"
|
||||
identifier="stack-editor"
|
||||
placeholder="# Define or paste the content of your docker-compose file here"
|
||||
yml="true"
|
||||
|
@ -173,7 +207,7 @@
|
|||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
ng-disabled="state.actionInProgress || stack.Status === 2 || !stackFileContent"
|
||||
ng-disabled="state.actionInProgress || stack.Status === 2 || !stackFileContent || orphaned"
|
||||
ng-click="deployStack()"
|
||||
button-spinner="state.actionInProgress"
|
||||
>
|
||||
|
@ -192,7 +226,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="containers">
|
||||
<div class="row" ng-if="containers && (!orphaned || orphanedRunning)">
|
||||
<div class="col-sm-12">
|
||||
<containers-datatable
|
||||
title-text="Containers"
|
||||
|
@ -206,7 +240,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="services">
|
||||
<div class="row" ng-if="services && (!orphaned || orphanedRunning)">
|
||||
<div class="col-sm-12">
|
||||
<services-datatable
|
||||
title-text="Services"
|
||||
|
@ -226,6 +260,6 @@
|
|||
</div>
|
||||
|
||||
<!-- access-control-panel -->
|
||||
<por-access-control-panel ng-if="stack" resource-id="stack.EndpointId + '_' + stack.Name" resource-control="stack.ResourceControl" resource-type="'stack'">
|
||||
<por-access-control-panel ng-if="stack && !orphaned" resource-id="stack.EndpointId + '_' + stack.Name" resource-control="stack.ResourceControl" resource-type="'stack'">
|
||||
</por-access-control-panel>
|
||||
<!-- !access-control-panel -->
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
|
||||
angular.module('portainer.app').controller('StackController', [
|
||||
'$async',
|
||||
'$q',
|
||||
|
@ -19,6 +21,8 @@ angular.module('portainer.app').controller('StackController', [
|
|||
'GroupService',
|
||||
'ModalService',
|
||||
'StackHelper',
|
||||
'ResourceControlService',
|
||||
'Authentication',
|
||||
'ContainerHelper',
|
||||
function (
|
||||
$async,
|
||||
|
@ -41,12 +45,13 @@ angular.module('portainer.app').controller('StackController', [
|
|||
GroupService,
|
||||
ModalService,
|
||||
StackHelper,
|
||||
ResourceControlService,
|
||||
Authentication,
|
||||
ContainerHelper
|
||||
) {
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
migrationInProgress: false,
|
||||
externalStack: false,
|
||||
showEditorTab: false,
|
||||
yamlError: false,
|
||||
isEditorDirty: false,
|
||||
|
@ -55,6 +60,7 @@ angular.module('portainer.app').controller('StackController', [
|
|||
$scope.formValues = {
|
||||
Prune: false,
|
||||
Endpoint: null,
|
||||
AccessControlData: new AccessControlFormData(),
|
||||
};
|
||||
|
||||
$window.onbeforeunload = () => {
|
||||
|
@ -162,6 +168,31 @@ angular.module('portainer.app').controller('StackController', [
|
|||
});
|
||||
}
|
||||
|
||||
$scope.associateStack = function () {
|
||||
var endpointId = +$state.params.endpointId;
|
||||
var stack = $scope.stack;
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
$scope.state.actionInProgress = true;
|
||||
|
||||
StackService.associate(stack, endpointId, $scope.orphanedRunning)
|
||||
.then(function success(data) {
|
||||
const resourceControl = data.ResourceControl;
|
||||
const userDetails = Authentication.getUserDetails();
|
||||
const userId = userDetails.ID;
|
||||
return ResourceControlService.applyResourceControl(userId, accessControlData, resourceControl);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Stack successfully associated', stack.Name);
|
||||
$state.go('docker.stacks');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to associate stack ' + stack.Name);
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deployStack = function () {
|
||||
var stackFile = $scope.stackFileContent;
|
||||
var env = FormHelper.removeInvalidEnvVars($scope.stack.Env);
|
||||
|
@ -391,14 +422,27 @@ angular.module('portainer.app').controller('StackController', [
|
|||
async function initView() {
|
||||
var stackName = $transition$.params().name;
|
||||
$scope.stackName = stackName;
|
||||
var external = $transition$.params().external;
|
||||
|
||||
$scope.currentEndpointId = EndpointProvider.endpointID();
|
||||
|
||||
if (external === 'true') {
|
||||
$scope.state.externalStack = true;
|
||||
const regular = $transition$.params().regular == 'true';
|
||||
$scope.regular = regular;
|
||||
|
||||
var external = $transition$.params().external == 'true';
|
||||
$scope.external = external;
|
||||
|
||||
const orphaned = $transition$.params().orphaned == 'true';
|
||||
$scope.orphaned = orphaned;
|
||||
|
||||
const orphanedRunning = $transition$.params().orphanedRunning == 'true';
|
||||
$scope.orphanedRunning = orphanedRunning;
|
||||
|
||||
if (external || (orphaned && orphanedRunning)) {
|
||||
loadExternalStack(stackName);
|
||||
} else {
|
||||
var stackId = $transition$.params().id;
|
||||
}
|
||||
|
||||
if (regular || orphaned) {
|
||||
const stackId = $transition$.params().id;
|
||||
loadStack(stackId);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,8 @@ function StacksController($scope, $state, Notifications, StackService, ModalServ
|
|||
var endpointMode = $scope.applicationState.endpoint.mode;
|
||||
var endpointId = EndpointProvider.endpointID();
|
||||
|
||||
StackService.stacks(true, endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER', endpointId)
|
||||
const includeOrphanedStacks = Authentication.isAdmin();
|
||||
StackService.stacks(true, endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER', endpointId, includeOrphanedStacks)
|
||||
.then(function success(data) {
|
||||
var stacks = data;
|
||||
$scope.stacks = stacks;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue