feat(docker/stacks): introduce date info for stacks (#4660)

* feat(docker/stacks): add creation and update dates

* feat(docker/stacks): put ownership column as the last column

* feat(docker/stacks): fix the no stacks message

* refactor(docker/stacks): make external stacks helpers more readable

* feat(docker/stacks): add updated and created by

* feat(docker/stacks): toggle updated column

* refactor(datatable): create column visibility component

Co-authored-by: alice groux <alice.grx@gmail.com>
pull/4609/head
Chaim Lev-Ari 2021-01-12 01:38:49 +02:00 committed by GitHub
parent b9fe8009dd
commit cbd7fdc62e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 202 additions and 190 deletions

View File

@ -7,11 +7,12 @@ import (
"regexp"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/security"
)
@ -60,13 +61,14 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
}
stackFolder := strconv.Itoa(int(stack.ID))
@ -89,6 +91,8 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@ -146,13 +150,14 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
}
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
@ -185,6 +190,8 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@ -242,13 +249,14 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
}
stackFolder := strconv.Itoa(int(stack.ID))
@ -271,6 +279,8 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}

View File

@ -6,11 +6,12 @@ import (
"path"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/security"
)
@ -55,14 +56,15 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
}
stackFolder := strconv.Itoa(int(stack.ID))
@ -85,6 +87,8 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@ -145,14 +149,15 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
}
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
@ -185,6 +190,8 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@ -249,14 +256,15 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
}
stackFolder := strconv.Itoa(int(stack.ID))
@ -279,6 +287,8 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}

View File

@ -4,12 +4,13 @@ import (
"errors"
"net/http"
"strconv"
"time"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
@ -135,6 +136,9 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta
return configErr
}
stack.UpdateDate = time.Now().Unix()
stack.UpdatedBy = config.user.Username
err = handler.deployComposeStack(config)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
@ -163,6 +167,9 @@ func (handler *Handler) updateSwarmStack(r *http.Request, stack *portainer.Stack
return configErr
}
stack.UpdateDate = time.Now().Unix()
stack.UpdatedBy = config.user.Username
err = handler.deploySwarmStack(config)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}

View File

@ -554,6 +554,10 @@ type (
Env []Pair `json:"Env"`
ResourceControl *ResourceControl `json:"ResourceControl"`
Status StackStatus `json:"Status"`
CreationDate int64
CreatedBy string
UpdateDate int64
UpdatedBy string
ProjectPath string
}

View File

@ -4,100 +4,8 @@
<div class="toolBar">
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
<div class="settings">
<span
class="setting"
ng-class="{ 'setting-active': $ctrl.columnVisibility.state.open }"
uib-dropdown
dropdown-append-to-body
auto-close="disabled"
is-open="$ctrl.columnVisibility.state.open"
>
<span uib-dropdown-toggle><i class="fa fa-columns space-right" aria-hidden="true"></i>Columns</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader">
Show / Hide Columns
</div>
<div class="menuContent">
<div class="md-checkbox">
<input
id="col_vis_state"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.state.display"
/>
<label for="col_vis_state" ng-bind="$ctrl.columnVisibility.columns.state.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_actions"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.actions.display"
/>
<label for="col_vis_actions" ng-bind="$ctrl.columnVisibility.columns.actions.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_stack"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.stack.display"
/>
<label for="col_vis_stack" ng-bind="$ctrl.columnVisibility.columns.stack.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_image"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.image.display"
/>
<label for="col_vis_image" ng-bind="$ctrl.columnVisibility.columns.image.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_created"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.created.display"
/>
<label for="col_vis_created" ng-bind="$ctrl.columnVisibility.columns.created.label"></label>
</div>
<div class="md-checkbox" ng-if="$ctrl.showHostColumn">
<input
id="col_vis_host"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.host.display"
/>
<label for="col_vis_host" ng-bind="$ctrl.columnVisibility.columns.host.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_ports"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.ports.display"
/>
<label for="col_vis_ports" ng-bind="$ctrl.columnVisibility.columns.ports.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_ownership"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.ownership.display"
/>
<label for="col_vis_ownership" ng-bind="$ctrl.columnVisibility.columns.ownership.label"></label>
</div>
</div>
<div>
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.columnVisibility.state.open = false;">Close</a>
</div>
</div>
</div>
</span>
<datatable-columns-visibility columns="$ctrl.columnVisibility.columns" on-change="($ctrl.onColumnVisibilityChange)"></datatable-columns-visibility>
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>

View File

@ -36,9 +36,6 @@ angular.module('portainer.docker').controller('ContainersDatatableController', [
};
this.columnVisibility = {
state: {
open: false,
},
columns: {
state: {
label: 'State',
@ -75,9 +72,11 @@ angular.module('portainer.docker').controller('ContainersDatatableController', [
},
};
this.onColumnVisibilityChange = function (columnVisibility) {
DatatableService.setColumnVisibilitySettings(this.tableKey, columnVisibility);
};
this.onColumnVisibilityChange = onColumnVisibilityChange.bind(this);
function onColumnVisibilityChange(columns) {
this.columnVisibility.columns = columns;
DatatableService.setColumnVisibilitySettings(this.tableKey, this.columnVisibility);
}
this.onSelectionChanged = function () {
this.updateSelectionState();
@ -199,7 +198,6 @@ angular.module('portainer.docker').controller('ContainersDatatableController', [
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
if (storedColumnVisibility !== null) {
this.columnVisibility = storedColumnVisibility;
this.columnVisibility.state.open = false;
}
};
},

View File

@ -0,0 +1,7 @@
export default class DatatableColumnsVisibilityController {
constructor() {
this.state = {
isOpen: false,
};
}
}

View File

@ -0,0 +1,19 @@
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.isOpen }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.state.isOpen">
<span uib-dropdown-toggle><i class="fa fa-columns space-right" aria-hidden="true"></i>Columns</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader">
Show / Hide Columns
</div>
<div class="menuContent">
<div class="md-checkbox" ng-repeat="(key, value) in $ctrl.columns">
<input id="col_vis_{{::key}}" ng-change="$ctrl.onChange($ctrl.columns)" type="checkbox" ng-model="value.display" />
<label for="col_vis_{{::key}}">{{ value.label }}</label>
</div>
</div>
<div>
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.state.isOpen = false;">Close</a>
</div>
</div>
</div>
</span>

View File

@ -0,0 +1,12 @@
import angular from 'angular';
import controller from './datatable-columns-visibility.controller';
angular.module('portainer.app').component('datatableColumnsVisibility', {
templateUrl: './datatable-columns-visibility.html',
controller,
bindings: {
columns: '<',
onChange: '<',
},
});

View File

@ -33,6 +33,7 @@ angular.module('portainer.app').controller('GenericDatatableController', [
refreshRate: '30',
},
};
this.resetSelectionState = function () {
this.state.selectAll = false;
this.state.selectedItems = [];
@ -158,6 +159,11 @@ angular.module('portainer.app').controller('GenericDatatableController', [
this.settings.open = false;
}
this.onSettingsRepeaterChange();
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
if (storedColumnVisibility !== null) {
this.columnVisibility = storedColumnVisibility;
}
};
/**

View File

@ -4,6 +4,7 @@
<div class="toolBar">
<div class="toolBarTitle"><i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
<div class="settings">
<datatable-columns-visibility columns="$ctrl.columnVisibility.columns" on-change="($ctrl.onColumnVisibilityChange)"></datatable-columns-visibility>
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
@ -117,6 +118,20 @@
</a>
</th>
<th>Control</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ResourceControl.CreationDate')">
Created
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.CreationDate' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.CreationDate' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th ng-if="$ctrl.columnVisibility.columns.updated.display">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.UpdateDate')">
Updated
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.UpdateDate' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.UpdateDate' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership
@ -154,6 +169,14 @@
</span>
<span ng-if="!item.External">Total</span>
</td>
<td>
<span ng-if="item.CreationDate">{{ item.CreationDate | getisodatefromtimestamp }} {{ item.CreatedBy ? 'by ' + item.CreatedBy : '' }}</span>
<span ng-if="!item.CreationDate"> - </span>
</td>
<td ng-if="$ctrl.columnVisibility.columns.updated.display">
<span ng-if="item.UpdateDate">{{ item.UpdateDate | getisodatefromtimestamp }} {{ item.UpdatedBy ? 'by ' + item.UpdatedBy : '' }}</span>
<span ng-if="!item.UpdateDate"> - </span>
</td>
<td>
<span>
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
@ -162,10 +185,10 @@
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="4" class="text-center text-muted">Loading...</td>
<td colspan="6" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="4" class="text-center text-muted">No stack available.</td>
<td colspan="6" class="text-center text-muted">No stack available.</td>
</tr>
</tbody>
</table>

View File

@ -15,6 +15,24 @@ angular.module('portainer.app').controller('StacksDatatableController', [
},
};
this.columnVisibility = {
state: {
open: false,
},
columns: {
updated: {
label: 'Updated',
display: false,
},
},
};
this.onColumnVisibilityChange = onColumnVisibilityChange.bind(this);
function onColumnVisibilityChange(columns) {
this.columnVisibility.columns = columns;
DatatableService.setColumnVisibilitySettings(this.tableKey, this.columnVisibility);
}
/**
* Do not allow external items
*/
@ -71,6 +89,11 @@ angular.module('portainer.app').controller('StacksDatatableController', [
this.settings.open = false;
}
this.onSettingsRepeaterChange();
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
if (storedColumnVisibility !== null) {
this.columnVisibility = storedColumnVisibility;
}
};
},
]);

View File

@ -1,37 +1,27 @@
import _ from 'lodash-es';
import { ExternalStackViewModel } from '@/portainer/models/stack';
angular.module('portainer.app').factory('StackHelper', [
function StackHelperFactory() {
'use strict';
var helper = {};
helper.getExternalStackNamesFromContainers = function (containers) {
var stackNames = [];
for (var i = 0; i < containers.length; i++) {
var container = containers[i];
if (!container.Labels || !container.Labels['com.docker.compose.project']) continue;
var stackName = container.Labels['com.docker.compose.project'];
stackNames.push(stackName);
}
return _.uniq(stackNames);
helper.getExternalStacksFromContainers = function (containers) {
return getExternalStacksFromLabel(containers, 'com.docker.compose.project', 2);
};
helper.getExternalStackNamesFromServices = function (services) {
var stackNames = [];
for (var i = 0; i < services.length; i++) {
var service = services[i];
if (!service.Labels || !service.Labels['com.docker.stack.namespace']) continue;
var stackName = service.Labels['com.docker.stack.namespace'];
stackNames.push(stackName);
}
return _.uniq(stackNames);
helper.getExternalStacksFromServices = function (services) {
return getExternalStacksFromLabel(services, 'com.docker.stack.namespace', 1);
};
function getExternalStacksFromLabel(items, label, type) {
return _.uniqBy(
items.filter((item) => item.Labels && item.Labels[label]).map((item) => new ExternalStackViewModel(item.Labels[label], type, item.Created)),
'Name'
);
}
return helper;
},
]);

View File

@ -13,11 +13,16 @@ export function StackViewModel(data) {
}
this.External = false;
this.Status = data.Status;
this.CreationDate = data.CreationDate;
this.CreatedBy = data.CreatedBy;
this.UpdateDate = data.UpdateDate;
this.UpdatedBy = data.UpdatedBy;
}
export function ExternalStackViewModel(name, type) {
export function ExternalStackViewModel(name, type, creationDate) {
this.Name = name;
this.Type = type;
this.External = true;
this.Checked = false;
this.CreationDate = creationDate;
}

View File

@ -1,5 +1,5 @@
import _ from 'lodash-es';
import { ExternalStackViewModel, StackViewModel } from '../../models/stack';
import { StackViewModel } from '../../models/stack';
angular.module('portainer.app').factory('StackService', [
'$q',
@ -121,13 +121,8 @@ angular.module('portainer.app').factory('StackService', [
var deferred = $q.defer();
ServiceService.services()
.then(function success(data) {
var services = data;
var stackNames = StackHelper.getExternalStackNamesFromServices(services);
var stacks = stackNames.map(function (name) {
return new ExternalStackViewModel(name, 1);
});
deferred.resolve(stacks);
.then(function success(services) {
deferred.resolve(StackHelper.getExternalStacksFromServices(services));
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve external stacks', err: err });
@ -140,13 +135,8 @@ angular.module('portainer.app').factory('StackService', [
var deferred = $q.defer();
ContainerService.containers(1)
.then(function success(data) {
var containers = data;
var stackNames = StackHelper.getExternalStackNamesFromContainers(containers);
var stacks = stackNames.map(function (name) {
return new ExternalStackViewModel(name, 2);
});
deferred.resolve(stacks);
.then(function success(containers) {
deferred.resolve(StackHelper.getExternalStacksFromContainers(containers));
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve external stacks', err: err });