diff --git a/api/bolt/datastore.go b/api/bolt/datastore.go
index a857375f3..c6ce7db61 100644
--- a/api/bolt/datastore.go
+++ b/api/bolt/datastore.go
@@ -128,6 +128,7 @@ func (store *Store) MigrateData() error {
ScheduleService: store.ScheduleService,
SettingsService: store.SettingsService,
StackService: store.StackService,
+ TagService: store.TagService,
TeamMembershipService: store.TeamMembershipService,
TemplateService: store.TemplateService,
UserService: store.UserService,
diff --git a/api/bolt/init.go b/api/bolt/init.go
index bc6e39be5..8e1a0661c 100644
--- a/api/bolt/init.go
+++ b/api/bolt/init.go
@@ -16,7 +16,7 @@ func (store *Store) Init() error {
Labels: []portainer.Pair{},
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
- Tags: []string{},
+ TagIDs: []portainer.TagID{},
}
err = store.EndpointGroupService.CreateEndpointGroup(unassignedGroup)
diff --git a/api/bolt/migrator/migrate_dbversion22.go b/api/bolt/migrator/migrate_dbversion22.go
new file mode 100644
index 000000000..f2e3c2b6c
--- /dev/null
+++ b/api/bolt/migrator/migrate_dbversion22.go
@@ -0,0 +1,57 @@
+package migrator
+
+import "github.com/portainer/portainer/api"
+
+func (m *Migrator) updateEndointsAndEndpointsGroupsToDBVersion23() error {
+ tags, err := m.tagService.Tags()
+ if err != nil {
+ return err
+ }
+
+ tagsNameMap := make(map[string]portainer.TagID)
+ for _, tag := range tags {
+ tagsNameMap[tag.Name] = tag.ID
+ }
+
+ endpoints, err := m.endpointService.Endpoints()
+ if err != nil {
+ return err
+ }
+
+ for _, endpoint := range endpoints {
+ endpointTags := make([]portainer.TagID, 0)
+ for _, tagName := range endpoint.Tags {
+ tagID, ok := tagsNameMap[tagName]
+ if ok {
+ endpointTags = append(endpointTags, tagID)
+ }
+ }
+ endpoint.TagIDs = endpointTags
+ err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
+ if err != nil {
+ return err
+ }
+ }
+
+ endpointGroups, err := m.endpointGroupService.EndpointGroups()
+ if err != nil {
+ return err
+ }
+
+ for _, endpointGroup := range endpointGroups {
+ endpointGroupTags := make([]portainer.TagID, 0)
+ for _, tagName := range endpointGroup.Tags {
+ tagID, ok := tagsNameMap[tagName]
+ if ok {
+ endpointGroupTags = append(endpointGroupTags, tagID)
+ }
+ }
+ endpointGroup.TagIDs = endpointGroupTags
+ err = m.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go
index 885cc9ab8..8f4aee085 100644
--- a/api/bolt/migrator/migrator.go
+++ b/api/bolt/migrator/migrator.go
@@ -12,6 +12,7 @@ import (
"github.com/portainer/portainer/api/bolt/schedule"
"github.com/portainer/portainer/api/bolt/settings"
"github.com/portainer/portainer/api/bolt/stack"
+ "github.com/portainer/portainer/api/bolt/tag"
"github.com/portainer/portainer/api/bolt/teammembership"
"github.com/portainer/portainer/api/bolt/template"
"github.com/portainer/portainer/api/bolt/user"
@@ -32,6 +33,7 @@ type (
scheduleService *schedule.Service
settingsService *settings.Service
stackService *stack.Service
+ tagService *tag.Service
teamMembershipService *teammembership.Service
templateService *template.Service
userService *user.Service
@@ -52,6 +54,7 @@ type (
ScheduleService *schedule.Service
SettingsService *settings.Service
StackService *stack.Service
+ TagService *tag.Service
TeamMembershipService *teammembership.Service
TemplateService *template.Service
UserService *user.Service
@@ -73,6 +76,7 @@ func NewMigrator(parameters *Parameters) *Migrator {
roleService: parameters.RoleService,
scheduleService: parameters.ScheduleService,
settingsService: parameters.SettingsService,
+ tagService: parameters.TagService,
teamMembershipService: parameters.TeamMembershipService,
templateService: parameters.TemplateService,
stackService: parameters.StackService,
@@ -301,5 +305,13 @@ func (m *Migrator) Migrate() error {
}
}
+ // Portainer 1.24.0-dev
+ if m.currentDBVersion < 23 {
+ err := m.updateEndointsAndEndpointsGroupsToDBVersion23()
+ if err != nil {
+ return err
+ }
+ }
+
return m.versionService.StoreDBVersion(portainer.DBVersion)
}
diff --git a/api/bolt/tag/tag.go b/api/bolt/tag/tag.go
index d54ee6b76..d4a5dc9de 100644
--- a/api/bolt/tag/tag.go
+++ b/api/bolt/tag/tag.go
@@ -52,6 +52,19 @@ func (service *Service) Tags() ([]portainer.Tag, error) {
return tags, err
}
+// Tag returns a tag by ID.
+func (service *Service) Tag(ID portainer.TagID) (*portainer.Tag, error) {
+ var tag portainer.Tag
+ identifier := internal.Itob(int(ID))
+
+ err := internal.GetObject(service.db, BucketName, identifier, &tag)
+ if err != nil {
+ return nil, err
+ }
+
+ return &tag, nil
+}
+
// CreateTag creates a new tag.
func (service *Service) CreateTag(tag *portainer.Tag) error {
return service.db.Update(func(tx *bolt.Tx) error {
diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go
index 236b64d35..46c5bb5ca 100644
--- a/api/cmd/portainer/main.go
+++ b/api/cmd/portainer/main.go
@@ -259,7 +259,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
LogoURL: *flags.Logo,
AuthenticationMethod: portainer.AuthenticationInternal,
LDAPSettings: portainer.LDAPSettings{
- AnonymousMode: true,
+ AnonymousMode: true,
AutoCreateUsers: true,
TLSConfig: portainer.TLSConfiguration{},
SearchSettings: []portainer.LDAPSearchSettings{
@@ -397,7 +397,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, endpointService portain
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
Extensions: []portainer.EndpointExtension{},
- Tags: []string{},
+ TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
}
@@ -440,7 +440,7 @@ func createUnsecuredEndpoint(endpointURL string, endpointService portainer.Endpo
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
Extensions: []portainer.EndpointExtension{},
- Tags: []string{},
+ TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
}
diff --git a/api/http/handler/endpointgroups/endpointgroup_create.go b/api/http/handler/endpointgroups/endpointgroup_create.go
index 32a617c92..34bdbe754 100644
--- a/api/http/handler/endpointgroups/endpointgroup_create.go
+++ b/api/http/handler/endpointgroups/endpointgroup_create.go
@@ -14,15 +14,15 @@ type endpointGroupCreatePayload struct {
Name string
Description string
AssociatedEndpoints []portainer.EndpointID
- Tags []string
+ TagIDs []portainer.TagID
}
func (payload *endpointGroupCreatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Name) {
return portainer.Error("Invalid endpoint group name")
}
- if payload.Tags == nil {
- payload.Tags = []string{}
+ if payload.TagIDs == nil {
+ payload.TagIDs = []portainer.TagID{}
}
return nil
}
@@ -40,7 +40,7 @@ func (handler *Handler) endpointGroupCreate(w http.ResponseWriter, r *http.Reque
Description: payload.Description,
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
- Tags: payload.Tags,
+ TagIDs: payload.TagIDs,
}
err = handler.EndpointGroupService.CreateEndpointGroup(endpointGroup)
diff --git a/api/http/handler/endpointgroups/endpointgroup_update.go b/api/http/handler/endpointgroups/endpointgroup_update.go
index 58ea605fc..ef7b4edc4 100644
--- a/api/http/handler/endpointgroups/endpointgroup_update.go
+++ b/api/http/handler/endpointgroups/endpointgroup_update.go
@@ -13,7 +13,7 @@ import (
type endpointGroupUpdatePayload struct {
Name string
Description string
- Tags []string
+ TagIDs []portainer.TagID
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
}
@@ -50,8 +50,8 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
endpointGroup.Description = payload.Description
}
- if payload.Tags != nil {
- endpointGroup.Tags = payload.Tags
+ if payload.TagIDs != nil {
+ endpointGroup.TagIDs = payload.TagIDs
}
updateAuthorizations := false
diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go
index 865923149..dfa030927 100644
--- a/api/http/handler/endpoints/endpoint_create.go
+++ b/api/http/handler/endpoints/endpoint_create.go
@@ -32,7 +32,7 @@ type endpointCreatePayload struct {
AzureApplicationID string
AzureTenantID string
AzureAuthenticationKey string
- Tags []string
+ TagIDs []portainer.TagID
}
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
@@ -54,14 +54,14 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
}
payload.GroupID = groupID
- var tags []string
- err = request.RetrieveMultiPartFormJSONValue(r, "Tags", &tags, true)
+ var tagIDs []portainer.TagID
+ err = request.RetrieveMultiPartFormJSONValue(r, "TagIds", &tagIDs, true)
if err != nil {
- return portainer.Error("Invalid Tags parameter")
+ return portainer.Error("Invalid TagIds parameter")
}
- payload.Tags = tags
- if payload.Tags == nil {
- payload.Tags = make([]string, 0)
+ payload.TagIDs = tagIDs
+ if payload.TagIDs == nil {
+ payload.TagIDs = make([]portainer.TagID, 0)
}
useTLS, _ := request.RetrieveBooleanMultiPartFormValue(r, "TLS", true)
@@ -187,7 +187,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
TeamAccessPolicies: portainer.TeamAccessPolicies{},
Extensions: []portainer.EndpointExtension{},
AzureCredentials: credentials,
- Tags: payload.Tags,
+ TagIDs: payload.TagIDs,
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
}
@@ -232,7 +232,7 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload)
AuthorizedUsers: []portainer.UserID{},
AuthorizedTeams: []portainer.TeamID{},
Extensions: []portainer.EndpointExtension{},
- Tags: payload.Tags,
+ TagIDs: payload.TagIDs,
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
EdgeKey: edgeKey,
@@ -278,7 +278,7 @@ func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload)
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
Extensions: []portainer.EndpointExtension{},
- Tags: payload.Tags,
+ TagIDs: payload.TagIDs,
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
}
@@ -322,7 +322,7 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload)
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
Extensions: []portainer.EndpointExtension{},
- Tags: payload.Tags,
+ TagIDs: payload.TagIDs,
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
}
diff --git a/api/http/handler/endpoints/endpoint_list.go b/api/http/handler/endpoints/endpoint_list.go
index 403a3f668..1d98296bf 100644
--- a/api/http/handler/endpoints/endpoint_list.go
+++ b/api/http/handler/endpoints/endpoint_list.go
@@ -5,7 +5,7 @@ import (
"strconv"
"strings"
- portainer "github.com/portainer/portainer/api"
+ "github.com/portainer/portainer/api"
"github.com/portainer/libhttp/request"
@@ -52,7 +52,15 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
}
if search != "" {
- filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, endpointGroups, search)
+ tags, err := handler.TagsService.Tags()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve tags from the database", err}
+ }
+ tagsMap := make(map[portainer.TagID]string)
+ for _, tag := range tags {
+ tagsMap[tag.ID] = tag.Name
+ }
+ filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, endpointGroups, tagsMap, search)
}
if endpointType != 0 {
@@ -102,17 +110,17 @@ func filterEndpointsByGroupID(endpoints []portainer.Endpoint, endpointGroupID po
return filteredEndpoints
}
-func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, searchCriteria string) []portainer.Endpoint {
+func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) []portainer.Endpoint {
filteredEndpoints := make([]portainer.Endpoint, 0)
for _, endpoint := range endpoints {
-
- if endpointMatchSearchCriteria(&endpoint, searchCriteria) {
+ endpointTags := convertTagIDsToTags(tagsMap, endpoint.TagIDs)
+ if endpointMatchSearchCriteria(&endpoint, endpointTags, searchCriteria) {
filteredEndpoints = append(filteredEndpoints, endpoint)
continue
}
- if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, searchCriteria) {
+ if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, tagsMap, searchCriteria) {
filteredEndpoints = append(filteredEndpoints, endpoint)
}
}
@@ -120,7 +128,7 @@ func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGro
return filteredEndpoints
}
-func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria string) bool {
+func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, searchCriteria string) bool {
if strings.Contains(strings.ToLower(endpoint.Name), searchCriteria) {
return true
}
@@ -134,8 +142,7 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria st
} else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" {
return true
}
-
- for _, tag := range endpoint.Tags {
+ for _, tag := range tags {
if strings.Contains(strings.ToLower(tag), searchCriteria) {
return true
}
@@ -144,14 +151,14 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria st
return false
}
-func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, searchCriteria string) bool {
+func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) bool {
for _, group := range endpointGroups {
if group.ID == endpoint.GroupID {
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
return true
}
-
- for _, tag := range group.Tags {
+ tags := convertTagIDsToTags(tagsMap, group.TagIDs)
+ for _, tag := range tags {
if strings.Contains(strings.ToLower(tag), searchCriteria) {
return true
}
@@ -172,3 +179,11 @@ func filterEndpointsByType(endpoints []portainer.Endpoint, endpointType portaine
}
return filteredEndpoints
}
+
+func convertTagIDsToTags(tagsMap map[portainer.TagID]string, tagIDs []portainer.TagID) []string {
+ tags := make([]string, 0)
+ for _, tagID := range tagIDs {
+ tags = append(tags, tagsMap[tagID])
+ }
+ return tags
+}
diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go
index dd87c22f2..7c02f5f67 100644
--- a/api/http/handler/endpoints/endpoint_update.go
+++ b/api/http/handler/endpoints/endpoint_update.go
@@ -24,7 +24,7 @@ type endpointUpdatePayload struct {
AzureApplicationID *string
AzureTenantID *string
AzureAuthenticationKey *string
- Tags []string
+ TagIDs []portainer.TagID
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
}
@@ -73,8 +73,8 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
endpoint.GroupID = portainer.EndpointGroupID(*payload.GroupID)
}
- if payload.Tags != nil {
- endpoint.Tags = payload.Tags
+ if payload.TagIDs != nil {
+ endpoint.TagIDs = payload.TagIDs
}
updateAuthorizations := false
diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go
index c655a0eef..6281a4a98 100644
--- a/api/http/handler/endpoints/handler.go
+++ b/api/http/handler/endpoints/handler.go
@@ -37,6 +37,7 @@ type Handler struct {
JobService portainer.JobService
ReverseTunnelService portainer.ReverseTunnelService
SettingsService portainer.SettingsService
+ TagsService portainer.TagService
AuthorizationService *portainer.AuthorizationService
}
diff --git a/api/http/handler/tags/handler.go b/api/http/handler/tags/handler.go
index 33cb59c9d..b5dac0274 100644
--- a/api/http/handler/tags/handler.go
+++ b/api/http/handler/tags/handler.go
@@ -12,7 +12,9 @@ import (
// Handler is the HTTP handler used to handle tag operations.
type Handler struct {
*mux.Router
- TagService portainer.TagService
+ TagService portainer.TagService
+ EndpointService portainer.EndpointService
+ EndpointGroupService portainer.EndpointGroupService
}
// NewHandler creates a handler to manage tag operations.
@@ -23,7 +25,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
h.Handle("/tags",
bouncer.AdminAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
h.Handle("/tags",
- bouncer.AdminAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet)
+ bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet)
h.Handle("/tags/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete)
diff --git a/api/http/handler/tags/tag_delete.go b/api/http/handler/tags/tag_delete.go
index 9c9e9d4e3..2467a38fd 100644
--- a/api/http/handler/tags/tag_delete.go
+++ b/api/http/handler/tags/tag_delete.go
@@ -15,6 +15,39 @@ func (handler *Handler) tagDelete(w http.ResponseWriter, r *http.Request) *httpe
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid tag identifier route variable", err}
}
+ tagID := portainer.TagID(id)
+
+ endpoints, err := handler.EndpointService.Endpoints()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
+ }
+
+ for _, endpoint := range endpoints {
+ tagIdx := findTagIndex(endpoint.TagIDs, tagID)
+ if tagIdx != -1 {
+ endpoint.TagIDs = removeElement(endpoint.TagIDs, tagIdx)
+ err = handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
+ if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint", err}
+ }
+ }
+ }
+
+ endpointGroups, err := handler.EndpointGroupService.EndpointGroups()
+ if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
+ }
+
+ for _, endpointGroup := range endpointGroups {
+ tagIdx := findTagIndex(endpointGroup.TagIDs, tagID)
+ if tagIdx != -1 {
+ endpointGroup.TagIDs = removeElement(endpointGroup.TagIDs, tagIdx)
+ err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
+ if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint group", err}
+ }
+ }
+ }
err = handler.TagService.DeleteTag(portainer.TagID(id))
if err != nil {
@@ -23,3 +56,21 @@ func (handler *Handler) tagDelete(w http.ResponseWriter, r *http.Request) *httpe
return response.Empty(w)
}
+
+func findTagIndex(tags []portainer.TagID, searchTagID portainer.TagID) int {
+ for idx, tagID := range tags {
+ if searchTagID == tagID {
+ return idx
+ }
+ }
+ return -1
+}
+
+func removeElement(arr []portainer.TagID, index int) []portainer.TagID {
+ if index < 0 {
+ return arr
+ }
+ lastTagIdx := len(arr) - 1
+ arr[index] = arr[lastTagIdx]
+ return arr[:lastTagIdx]
+}
diff --git a/api/http/server.go b/api/http/server.go
index 066bc7bef..eb4777071 100644
--- a/api/http/server.go
+++ b/api/http/server.go
@@ -7,7 +7,7 @@ import (
"github.com/portainer/portainer/api/http/handler/roles"
- "github.com/portainer/portainer/api"
+ portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/http/handler"
"github.com/portainer/portainer/api/http/handler/auth"
@@ -222,6 +222,8 @@ func (server *Server) Start() error {
var tagHandler = tags.NewHandler(requestBouncer)
tagHandler.TagService = server.TagService
+ tagHandler.EndpointService = server.EndpointService
+ tagHandler.EndpointGroupService = server.EndpointGroupService
var teamHandler = teams.NewHandler(requestBouncer)
teamHandler.TeamService = server.TeamService
diff --git a/api/portainer.go b/api/portainer.go
index 23b149c82..f0339677d 100644
--- a/api/portainer.go
+++ b/api/portainer.go
@@ -260,7 +260,7 @@ type (
TLSConfig TLSConfiguration `json:"TLSConfig"`
Extensions []EndpointExtension `json:"Extensions"`
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
- Tags []string `json:"Tags"`
+ TagIDs []TagID `json:"TagIds"`
Status EndpointStatus `json:"Status"`
Snapshots []Snapshot `json:"Snapshots"`
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
@@ -277,6 +277,9 @@ type (
// Deprecated in DBVersion == 18
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
+
+ // Deprecated in DBVersion == 22
+ Tags []string `json:"Tags"`
}
// Authorization represents an authorization associated to an operation
@@ -426,7 +429,7 @@ type (
Description string `json:"Description"`
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
- Tags []string `json:"Tags"`
+ TagIDs []TagID `json:"TagIds"`
// Deprecated fields
Labels []Pair `json:"Labels"`
@@ -434,6 +437,9 @@ type (
// Deprecated in DBVersion == 18
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
+
+ // Deprecated in DBVersion == 22
+ Tags []string `json:"Tags"`
}
// EndpointExtension represents a deprecated form of Portainer extension
@@ -775,6 +781,7 @@ type (
// TagService represents a service for managing tag data
TagService interface {
Tags() ([]Tag, error)
+ Tag(ID TagID) (*Tag, error)
CreateTag(tag *Tag) error
DeleteTag(ID TagID) error
}
@@ -919,7 +926,7 @@ const (
// APIVersion is the version number of the Portainer API
APIVersion = "1.24.0-dev"
// DBVersion is the version number of the Portainer database
- DBVersion = 22
+ DBVersion = 23
// AssetsServerURL represents the URL of the Portainer asset server
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved
diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js b/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js
index 62433ee25..61be9faf0 100644
--- a/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js
+++ b/app/portainer/components/endpoint-list/endpoint-item/endpoint-item-controller.js
@@ -1,12 +1,42 @@
-angular.module('portainer.app').controller('EndpointItemController', [
- function EndpointItemController() {
- var ctrl = this;
+import angular from 'angular';
+import _ from 'lodash-es';
+import PortainerEndpointTagHelper from 'Portainer/helpers/tagHelper';
- ctrl.editEndpoint = editEndpoint;
-
- function editEndpoint(event) {
- event.stopPropagation();
- ctrl.onEdit(ctrl.model.Id);
- }
+class EndpointItemController {
+ /* @ngInject */
+ constructor() {
+ this.editEndpoint = this.editEndpoint.bind(this);
}
-]);
+
+ editEndpoint(event) {
+ event.stopPropagation();
+ this.onEdit(this.model.Id);
+ }
+
+ joinTags() {
+ if (!this.tags) {
+ return 'Loading tags...';
+ }
+
+ if (!this.model.TagIds || !this.model.TagIds.length) {
+ return '';
+ }
+
+ const tagNames = PortainerEndpointTagHelper.idsToTagNames(this.tags, this.model.TagIds);
+ return _.join(tagNames, ',')
+ }
+
+ $onInit() {
+ this.endpointTags = this.joinTags();
+ }
+
+ $onChanges({ tags, model }) {
+ if ((!tags && !model) || (!tags.currentValue && !model.currentValue)) {
+ return;
+ }
+ this.endpointTags = this.joinTags();
+ }
+}
+
+angular.module('portainer.app').controller('EndpointItemController', EndpointItemController);
+export default EndpointItemController;
diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html
index 5060848a0..c8d9f4457 100644
--- a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html
+++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html
@@ -85,14 +85,12 @@
-
-
+
No tags
-
+
-
- {{ tag }}{{ $last? '' : ', ' }}
-
+ {{ $ctrl.endpointTags }}
diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js
index ab12b75a6..ec8f2b1fa 100644
--- a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js
+++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js
@@ -1,10 +1,15 @@
+import angular from 'angular';
+
+import EndpointItemController from './endpoint-item-controller';
+
angular.module('portainer.app').component('endpointItem', {
templateUrl: './endpointItem.html',
bindings: {
model: '<',
onSelect: '<',
onEdit: '<',
- isAdmin:'<'
+ isAdmin: '<',
+ tags: '<',
},
- controller: 'EndpointItemController'
+ controller: EndpointItemController,
});
diff --git a/app/portainer/components/endpoint-list/endpoint-list-controller.js b/app/portainer/components/endpoint-list/endpoint-list-controller.js
index 0abf59e8e..ca5fe52d9 100644
--- a/app/portainer/components/endpoint-list/endpoint-list-controller.js
+++ b/app/portainer/components/endpoint-list/endpoint-list-controller.js
@@ -29,12 +29,12 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
if (this.hasBackendPagination()) {
this.paginationChangedAction();
} else {
- this.state.filteredEndpoints = frontEndpointFilter(this.endpoints, filterValue);
+ this.state.filteredEndpoints = frontEndpointFilter(this.endpoints, this.tags, filterValue);
this.state.loading = false;
}
}
- function frontEndpointFilter(endpoints, filterValue) {
+ function frontEndpointFilter(endpoints, tags, filterValue) {
if (!endpoints || !endpoints.length || !filterValue) {
return endpoints;
}
@@ -47,8 +47,12 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
_.includes(endpoint.Name.toLowerCase(), lowerCaseKeyword) ||
_.includes(endpoint.GroupName.toLowerCase(), lowerCaseKeyword) ||
_.includes(endpoint.URL.toLowerCase(), lowerCaseKeyword) ||
- _.some(endpoint.Tags, function(tag) {
- return _.includes(tag.toLowerCase(), lowerCaseKeyword);
+ _.some(endpoint.TagIds, tagId => {
+ const tag = tags.find(t => t.Id === tagId);
+ if (!tag) {
+ return false;
+ }
+ return _.includes(tag.Name.toLowerCase(), lowerCaseKeyword);
}) ||
_.includes(statusString, keyword)
);
diff --git a/app/portainer/components/endpoint-list/endpoint-list.js b/app/portainer/components/endpoint-list/endpoint-list.js
index 06835c9e0..1bf184749 100644
--- a/app/portainer/components/endpoint-list/endpoint-list.js
+++ b/app/portainer/components/endpoint-list/endpoint-list.js
@@ -5,6 +5,7 @@ angular.module('portainer.app').component('endpointList', {
titleText: '@',
titleIcon: '@',
endpoints: '<',
+ tags: '<',
tableKey: '@',
dashboardAction: '<',
snapshotAction: '<',
diff --git a/app/portainer/components/endpoint-list/endpointList.html b/app/portainer/components/endpoint-list/endpointList.html
index 028ccc122..d6fd453a3 100644
--- a/app/portainer/components/endpoint-list/endpointList.html
+++ b/app/portainer/components/endpoint-list/endpointList.html
@@ -33,6 +33,7 @@
on-select="$ctrl.dashboardAction"
on-edit="$ctrl.editAction"
is-admin="$ctrl.isAdmin"
+ tags="$ctrl.tags"
>