diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index 6ceab991c..fb3688dbf 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -7,6 +7,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/asaskevich/govalidator" httperror "github.com/portainer/libhttp/error" @@ -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)) @@ -146,13 +148,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))) @@ -242,13 +245,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)) diff --git a/api/http/handler/stacks/create_swarm_stack.go b/api/http/handler/stacks/create_swarm_stack.go index 0113e8a41..bd65f3e3b 100644 --- a/api/http/handler/stacks/create_swarm_stack.go +++ b/api/http/handler/stacks/create_swarm_stack.go @@ -6,6 +6,7 @@ import ( "path" "strconv" "strings" + "time" "github.com/asaskevich/govalidator" httperror "github.com/portainer/libhttp/error" @@ -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)) @@ -145,14 +147,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))) @@ -249,14 +252,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)) diff --git a/api/http/handler/stacks/stack_update.go b/api/http/handler/stacks/stack_update.go index df4178f8f..4ee04f5f9 100644 --- a/api/http/handler/stacks/stack_update.go +++ b/api/http/handler/stacks/stack_update.go @@ -4,6 +4,7 @@ import ( "errors" "net/http" "strconv" + "time" "github.com/asaskevich/govalidator" httperror "github.com/portainer/libhttp/error" @@ -123,6 +124,7 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta } stack.Env = payload.Env + stack.UpdateDate = time.Now().Unix() stackFolder := strconv.Itoa(int(stack.ID)) _, err = handler.FileService.StoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent)) @@ -151,6 +153,7 @@ func (handler *Handler) updateSwarmStack(r *http.Request, stack *portainer.Stack } stack.Env = payload.Env + stack.UpdateDate = time.Now().Unix() stackFolder := strconv.Itoa(int(stack.ID)) _, err = handler.FileService.StoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent)) diff --git a/api/portainer.go b/api/portainer.go index e68e0c223..4a98d4d89 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -554,6 +554,8 @@ type ( Env []Pair `json:"Env"` ResourceControl *ResourceControl `json:"ResourceControl"` Status StackStatus `json:"Status"` + CreationDate int64 `json:"CreationDate"` + UpdateDate int64 `json:"UpdateDate"` ProjectPath string } diff --git a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html index 1bd2c313d..a2ff2eb58 100644 --- a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html +++ b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html @@ -117,6 +117,20 @@ Control + + + Created + + + + + + + Updated + + + + Ownership @@ -154,6 +168,14 @@ Total + + {{ item.CreationDate | getisodatefromtimestamp }} + - + + + {{ item.UpdateDate | getisodatefromtimestamp }} + - + @@ -165,7 +187,7 @@ Loading... - No stack available. + No stack available. diff --git a/app/portainer/helpers/stackHelper.js b/app/portainer/helpers/stackHelper.js index b9beb6172..d5c06501b 100644 --- a/app/portainer/helpers/stackHelper.js +++ b/app/portainer/helpers/stackHelper.js @@ -5,31 +5,31 @@ angular.module('portainer.app').factory('StackHelper', [ 'use strict'; var helper = {}; - helper.getExternalStackNamesFromContainers = function (containers) { - var stackNames = []; + helper.getExternalStacksFromContainers = function (containers) { + var stacks = []; 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); + stacks.push({ stackName, creationDate: container.Created }); } - return _.uniq(stackNames); + return _.uniq(stacks); }; - helper.getExternalStackNamesFromServices = function (services) { - var stackNames = []; + helper.getExternalStacksFromServices = function (services) { + var stacks = []; 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); + stacks.push({ stackName, creationDate: service.Created }); } - return _.uniq(stackNames); + return _.uniq(stacks); }; return helper; diff --git a/app/portainer/models/stack.js b/app/portainer/models/stack.js index ef28f97e3..6bcb826f9 100644 --- a/app/portainer/models/stack.js +++ b/app/portainer/models/stack.js @@ -13,11 +13,14 @@ export function StackViewModel(data) { } this.External = false; this.Status = data.Status; + this.CreationDate = data.CreationDate; + this.UpdateDate = data.UpdateDate; } -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; } diff --git a/app/portainer/services/api/stackService.js b/app/portainer/services/api/stackService.js index 68bebbd56..ad6519903 100644 --- a/app/portainer/services/api/stackService.js +++ b/app/portainer/services/api/stackService.js @@ -123,9 +123,9 @@ angular.module('portainer.app').factory('StackService', [ 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); + var stackDatas = StackHelper.getExternalStacksFromServices(services); + var stacks = stackDatas.map(function (stack) { + return new ExternalStackViewModel(stack.stackName, 1, stack.creationDate); }); deferred.resolve(stacks); }) @@ -142,9 +142,9 @@ angular.module('portainer.app').factory('StackService', [ 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); + var stacksDatas = StackHelper.getExternalStacksFromContainers(containers); + var stacks = stacksDatas.map(function (stack) { + return new ExternalStackViewModel(stack.stackName, 2, stack.creationDate); }); deferred.resolve(stacks); })