mirror of https://github.com/portainer/portainer
refactor(tags): refactor tag management (#3628)
* refactor(tags): replace tags with tag ids * refactor(tags): revert tags to be strings and add tagids * refactor(tags): enable search by tag in home view * refactor(tags): show endpoint tags * refactor(endpoints): expect tagIds on create payload * refactor(endpoints): expect tagIds on update payload * refactor(endpoints): replace TagIds to TagIDs * refactor(endpoints): set endpoint group to get TagIDs * refactor(endpoints): refactor tag-selector to receive tag-ids * refactor(endpoints): show tags in multi-endpoint-selector * chore(tags): revert reformat * refactor(endpoints): remove unneeded bind * refactor(endpoints): change param tags to tagids in endpoint create * refactor(endpoints): remove console.log * refactor(tags): remove deleted tag from endpoint and endpoint group * fix(endpoints): show loading label while loading tags * chore(go): remove obsolete import labels * chore(db): add db version comment * fix(db): add tag service to migrator * refactor(db): add error checks in migrator * style(db): sort props in alphabetical order * style(tags): fix typo Co-Authored-By: Anthony Lapenna <anthony.lapenna@portainer.io> * refactor(endpoints): replace tagsMap with tag string representation * refactor(tags): rewrite tag delete to be more readable * refactor(home): rearange code to match former style * refactor(tags): guard against missing model in tag-selector * refactor(tags): rename vars in tag_delete * refactor(tags): allow any authenticated user to fetch tag list * refactor(endpoints): replace controller function with class * refactor(endpoints): replace function with helper * refactor(endpoints): replace controller with class * refactor(tags): revert tags-selector to use 1 way bindings * refactor(endpoints): load empty tag array instead of nil * refactor(endpoints): revert default tag ids * refactor(endpoints): use function in place * refactor(tags): use lodash * style(tags): use parens in arrow functions * fix(tags): remove tag from tag model Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>pull/3657/head
parent
fe89a4fc01
commit
edd86f2506
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -37,6 +37,7 @@ type Handler struct {
|
|||
JobService portainer.JobService
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
SettingsService portainer.SettingsService
|
||||
TagsService portainer.TagService
|
||||
AuthorizationService *portainer.AuthorizationService
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -85,14 +85,12 @@
|
|||
</span>
|
||||
<span class="space-left space-right">-</span>
|
||||
</span>
|
||||
<span ng-if="$ctrl.model.Tags.length === 0">
|
||||
<span ng-if="$ctrl.endpointTags.length === 0">
|
||||
<i class="fa fa-tags" aria-hidden="true"></i> No tags
|
||||
</span>
|
||||
<span ng-if="$ctrl.model.Tags.length > 0">
|
||||
<span ng-if="$ctrl.endpointTags.length > 0">
|
||||
<i class="fa fa-tags" aria-hidden="true"></i>
|
||||
<span ng-repeat="tag in $ctrl.model.Tags">
|
||||
{{ tag }}{{ $last? '' : ', ' }}
|
||||
</span>
|
||||
{{ $ctrl.endpointTags }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="small text-muted" ng-if="$ctrl.model.Type !== 4">
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@ angular.module('portainer.app').component('endpointList', {
|
|||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
endpoints: '<',
|
||||
tags: '<',
|
||||
tableKey: '@',
|
||||
dashboardAction: '<',
|
||||
snapshotAction: '<',
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
on-select="$ctrl.dashboardAction"
|
||||
on-edit="$ctrl.editAction"
|
||||
is-admin="$ctrl.isAdmin"
|
||||
tags="$ctrl.tags"
|
||||
></endpoint-item>
|
||||
<endpoint-item ng-if="!$ctrl.hasBackendPagination()"
|
||||
dir-paginate="endpoint in $ctrl.state.filteredEndpoints | itemsPerPage: $ctrl.state.paginatedItemLimit"
|
||||
|
@ -40,6 +41,7 @@
|
|||
on-select="$ctrl.dashboardAction"
|
||||
on-edit="$ctrl.editAction"
|
||||
is-admin="$ctrl.isAdmin"
|
||||
tags="$ctrl.tags"
|
||||
></endpoint-item>
|
||||
<div ng-if="$ctrl.state.loading" class="text-center text-muted">
|
||||
Loading...
|
||||
|
|
|
@ -28,8 +28,9 @@
|
|||
<!-- tags -->
|
||||
<div class="form-group">
|
||||
<tag-selector
|
||||
ng-if="$ctrl.availableTags && $ctrl.model.TagIds"
|
||||
tags="$ctrl.availableTags"
|
||||
model="$ctrl.model.Tags"
|
||||
model="$ctrl.model.TagIds"
|
||||
></tag-selector>
|
||||
</div>
|
||||
<!-- !tags -->
|
||||
|
|
|
@ -69,6 +69,7 @@ angular.module('portainer.app').component('scheduleForm', {
|
|||
model: '=',
|
||||
endpoints: '<',
|
||||
groups: '<',
|
||||
tags: '<',
|
||||
addLabelAction: '<',
|
||||
removeLabelAction: '<',
|
||||
formAction: '<',
|
||||
|
|
|
@ -265,9 +265,9 @@
|
|||
</div>
|
||||
<!-- node-selection -->
|
||||
<multi-endpoint-selector
|
||||
ng-if="$ctrl.endpoints && $ctrl.groups"
|
||||
ng-if="$ctrl.endpoints && $ctrl.groups && $ctrl.tags"
|
||||
model="$ctrl.model.Job.Endpoints"
|
||||
endpoints="$ctrl.endpoints" groups="$ctrl.groups"
|
||||
endpoints="$ctrl.endpoints" groups="$ctrl.groups" tags="$ctrl.tags"
|
||||
></multi-endpoint-selector>
|
||||
<!-- !node-selection -->
|
||||
<!-- actions -->
|
||||
|
|
|
@ -2,8 +2,9 @@ angular.module('portainer.app').component('multiEndpointSelector', {
|
|||
templateUrl: './multiEndpointSelector.html',
|
||||
controller: 'MultiEndpointSelectorController',
|
||||
bindings: {
|
||||
'model': '=',
|
||||
'endpoints': '<',
|
||||
'groups': '<'
|
||||
}
|
||||
model: '=',
|
||||
endpoints: '<',
|
||||
groups: '<',
|
||||
tags: '<',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
<ui-select-match placeholder="Select one or multiple endpoint(s)">
|
||||
<span>
|
||||
{{ $item.Name }}
|
||||
<span ng-if="$item.Tags.length">({{ $item.Tags | arraytostr }})</span>
|
||||
</span>
|
||||
<span ng-if="$item.TagIds.length">({{ $ctrl.tagIdsToTagNames($item.TagIds) | arraytostr }})</span>
|
||||
</span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices group-by="$ctrl.groupEndpoints" group-filter="$ctrl.sortGroups" repeat="endpoint.Id as endpoint in $ctrl.endpoints | filter: { Name: $select.search }">
|
||||
<span>
|
||||
{{ endpoint.Name }}
|
||||
<span ng-if="endpoint.Tags.length">({{ endpoint.Tags | arraytostr }})</span>
|
||||
</span>
|
||||
<span ng-if="endpoint.TagIds.length">({{ $ctrl.tagIdsToTagNames(endpoint.TagIds) | arraytostr }})</span>
|
||||
</span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
|
|
|
@ -1,37 +1,40 @@
|
|||
import _ from 'lodash-es';
|
||||
import PortainerEndpointTagHelper from 'Portainer/helpers/tagHelper';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('MultiEndpointSelectorController', function () {
|
||||
var ctrl = this;
|
||||
import angular from 'angular';
|
||||
|
||||
this.sortGroups = function(groups) {
|
||||
class MultiEndpointSelectorController {
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
this.sortGroups = this.sortGroups.bind(this);
|
||||
this.groupEndpoints = this.groupEndpoints.bind(this);
|
||||
this.tagIdsToTagNames = this.tagIdsToTagNames.bind(this);
|
||||
}
|
||||
|
||||
sortGroups(groups) {
|
||||
return _.sortBy(groups, ['name']);
|
||||
};
|
||||
}
|
||||
|
||||
this.groupEndpoints = function(endpoint) {
|
||||
for (var i = 0; i < ctrl.availableGroups.length; i++) {
|
||||
var group = ctrl.availableGroups[i];
|
||||
groupEndpoints(endpoint) {
|
||||
for (var i = 0; i < this.availableGroups.length; i++) {
|
||||
var group = this.availableGroups[i];
|
||||
|
||||
if (endpoint.GroupId === group.Id) {
|
||||
return group.Name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.$onInit = function() {
|
||||
this.availableGroups = filterEmptyGroups(this.groups, this.endpoints);
|
||||
};
|
||||
|
||||
function filterEmptyGroups(groups, endpoints) {
|
||||
return groups.filter(function f(group) {
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
|
||||
var endpoint = endpoints[i];
|
||||
if (endpoint.GroupId === group.Id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tagIdsToTagNames(tagIds) {
|
||||
return PortainerEndpointTagHelper.idsToTagNames(this.tags, tagIds);
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.availableGroups = _.filter(this.groups, group =>
|
||||
_.some(this.endpoints, endpoint => endpoint.GroupId == group.Id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MultiEndpointSelectorController;
|
||||
angular.module('portainer.app').controller('MultiEndpointSelectorController', MultiEndpointSelectorController);
|
||||
|
|
|
@ -3,6 +3,6 @@ angular.module('portainer.app').component('tagSelector', {
|
|||
controller: 'TagSelectorController',
|
||||
bindings: {
|
||||
tags: '<',
|
||||
model: '='
|
||||
}
|
||||
model: '=',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
Selected tags
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10" style="padding-top: 4px;">
|
||||
<span class="tag space-right interactive" ng-repeat="tag in $ctrl.model" ng-click="$ctrl.removeTag(tag)">
|
||||
{{ tag }}
|
||||
<span class="tag space-right interactive" ng-repeat="tag in $ctrl.state.selectedTags" ng-click="$ctrl.removeTag(tag)">
|
||||
{{ tag.Name }}
|
||||
<a title="Remove tag" ng-click="$ctrl.removeTag(tag)" style="margin-left: 2px;">
|
||||
<span class="fa fa-trash-alt white-icon" aria-hidden="true"></span>
|
||||
</a>
|
||||
|
@ -20,10 +20,11 @@
|
|||
type="text" ng-model="$ctrl.state.selectedValue"
|
||||
id="tags" class="form-control"
|
||||
placeholder="Select tags..."
|
||||
uib-typeahead="tag for tag in $ctrl.tags | filter:$viewValue | limitTo:7"
|
||||
uib-typeahead="tag.Id as tag.Name for tag in $ctrl.tags | filter: $ctrl.filterSelected | filter:$viewValue | limitTo:7"
|
||||
typeahead-on-select="$ctrl.selectTag($item, $model, $label)"
|
||||
typeahead-no-results="$ctrl.state.noResult"
|
||||
typeahead-show-hint="true" typeahead-min-length="0" />
|
||||
typeahead-show-hint="true" typeahead-min-length="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-sm-9 col-lg-10" ng-if="$ctrl.tags.length === 0">
|
||||
<span class="small text-muted">
|
||||
|
|
|
@ -1,32 +1,35 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('TagSelectorController', function () {
|
||||
|
||||
this.$onChanges = function(changes) {
|
||||
if(angular.isDefined(changes.tags.currentValue)) {
|
||||
this.tags = _.difference(changes.tags.currentValue, this.model);
|
||||
}
|
||||
angular.module('portainer.app').controller('TagSelectorController', function() {
|
||||
this.$onInit = function() {
|
||||
this.state.selectedTags = _.map(this.model, (id) => _.find(this.tags, (t) => t.Id === id));
|
||||
};
|
||||
|
||||
this.state = {
|
||||
selectedValue: '',
|
||||
noResult: false
|
||||
selectedTags: [],
|
||||
noResult: false,
|
||||
};
|
||||
|
||||
this.selectTag = function($item) {
|
||||
this.state.selectedValue = '';
|
||||
this.model.push($item);
|
||||
this.tags = _.remove(this.tags, function(item) {
|
||||
return item !== $item;
|
||||
});
|
||||
this.model.push($item.Id);
|
||||
this.state.selectedTags.push($item);
|
||||
};
|
||||
|
||||
this.removeTag = function(tag) {
|
||||
var idx = this.model.indexOf(tag);
|
||||
if (idx > -1) {
|
||||
this.model.splice(idx, 1);
|
||||
this.tags.push(tag);
|
||||
}
|
||||
this.removeTag = function removeTag(tag) {
|
||||
_.remove(this.state.selectedTags, { Id: tag.Id });
|
||||
_.remove(this.model, (id) => id === tag.Id);
|
||||
};
|
||||
|
||||
this.filterSelected = filterSelected.bind(this);
|
||||
|
||||
function filterSelected($item) {
|
||||
if (!this.model) {
|
||||
return true;
|
||||
}
|
||||
return !_.includes(this.model, $item.Id);
|
||||
}
|
||||
window._remove = _.remove;
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
export default class PortainerEndpointTagHelper {
|
||||
static idsToTagNames(tags, ids) {
|
||||
const filteredTags = _.filter(tags, tag => _.includes(ids, tag.Id));
|
||||
const tagNames = _.map(filteredTags, 'Name');
|
||||
return tagNames;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
export function EndpointGroupDefaultModel() {
|
||||
this.Name = '';
|
||||
this.Description = '';
|
||||
this.Tags = [];
|
||||
this.TagIds = [];
|
||||
}
|
||||
|
||||
export function EndpointGroupModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Description = data.Description;
|
||||
this.Tags = data.Tags;
|
||||
this.TagIds = data.TagIds;
|
||||
this.AuthorizedUsers = data.AuthorizedUsers;
|
||||
this.AuthorizedTeams = data.AuthorizedTeams;
|
||||
this.UserAccessPolicies = data.UserAccessPolicies;
|
||||
|
@ -18,7 +18,7 @@ export function EndpointGroupModel(data) {
|
|||
export function EndpointGroupCreateRequest(model, endpoints) {
|
||||
this.Name = model.Name;
|
||||
this.Description = model.Description;
|
||||
this.Tags = model.Tags;
|
||||
this.TagIds = model.TagIds;
|
||||
this.AssociatedEndpoints = endpoints;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ export function EndpointGroupUpdateRequest(model, endpoints) {
|
|||
this.id = model.Id;
|
||||
this.Name = model.Name;
|
||||
this.Description = model.Description;
|
||||
this.Tags = model.Tags;
|
||||
this.TagIds = model.TagIds;
|
||||
this.AssociatedEndpoints = endpoints;
|
||||
this.UserAccessPolicies = model.UserAccessPolicies;
|
||||
this.TeamAccessPolicies = model.TeamAccessPolicies;
|
||||
|
|
|
@ -63,7 +63,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
var endpointURL = URL;
|
||||
|
@ -71,7 +71,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|||
endpointURL = 'tcp://' + URL;
|
||||
}
|
||||
|
||||
FileUploadService.createEndpoint(name, type, endpointURL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
FileUploadService.createEndpoint(name, type, endpointURL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
})
|
||||
|
@ -82,10 +82,10 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tags) {
|
||||
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags)
|
||||
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
|
||||
.then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
})
|
||||
|
|
|
@ -100,7 +100,7 @@ angular.module('portainer.app')
|
|||
});
|
||||
};
|
||||
|
||||
service.createEndpoint = function(name, type, URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
service.createEndpoint = function(name, type, URL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
|
@ -109,7 +109,7 @@ angular.module('portainer.app')
|
|||
URL: URL,
|
||||
PublicURL: PublicURL,
|
||||
GroupID: groupID,
|
||||
Tags: Upload.json(tags),
|
||||
TagIds: Upload.json(tagIds),
|
||||
TLS: TLS,
|
||||
TLSSkipVerify: TLSSkipVerify,
|
||||
TLSSkipClientVerify: TLSSkipClientVerify,
|
||||
|
@ -121,14 +121,14 @@ angular.module('portainer.app')
|
|||
});
|
||||
};
|
||||
|
||||
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tags) {
|
||||
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
||||
return Upload.upload({
|
||||
url: 'api/endpoints',
|
||||
data: {
|
||||
Name: name,
|
||||
EndpointType: 3,
|
||||
GroupID: groupId,
|
||||
Tags: Upload.json(tags),
|
||||
TagIds: Upload.json(tagIds),
|
||||
AzureApplicationID: applicationId,
|
||||
AzureTenantID: tenantId,
|
||||
AzureAuthenticationKey: authenticationKey
|
||||
|
|
|
@ -18,7 +18,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
AzureApplicationId: '',
|
||||
AzureTenantId: '',
|
||||
AzureAuthenticationKey: '',
|
||||
Tags: []
|
||||
TagIds: []
|
||||
};
|
||||
|
||||
$scope.copyAgentCommand = function() {
|
||||
|
@ -40,7 +40,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
||||
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
|
||||
var groupId = $scope.formValues.GroupId;
|
||||
var tags = $scope.formValues.Tags;
|
||||
var tagIds = $scope.formValues.TagIds;
|
||||
|
||||
var securityData = $scope.formValues.SecurityFormData;
|
||||
var TLS = securityData.TLS;
|
||||
|
@ -51,7 +51,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
|
||||
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
|
||||
|
||||
addEndpoint(name, 1, URL, publicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
||||
addEndpoint(name, 1, URL, publicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
||||
};
|
||||
|
||||
$scope.addAgentEndpoint = function() {
|
||||
|
@ -59,18 +59,18 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
||||
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
|
||||
var groupId = $scope.formValues.GroupId;
|
||||
var tags = $scope.formValues.Tags;
|
||||
var tagIds = $scope.formValues.TagIds;
|
||||
|
||||
addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null);
|
||||
addEndpoint(name, 2, URL, publicURL, groupId, tagIds, true, true, true, null, null, null);
|
||||
};
|
||||
|
||||
$scope.addEdgeAgentEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var groupId = $scope.formValues.GroupId;
|
||||
var tags = $scope.formValues.Tags;
|
||||
var tagIds = $scope.formValues.TagIds;
|
||||
var URL = $scope.formValues.URL;
|
||||
|
||||
addEndpoint(name, 4, URL, "", groupId, tags, false, false, false, null, null, null);
|
||||
addEndpoint(name, 4, URL, "", groupId, tagIds, false, false, false, null, null, null);
|
||||
};
|
||||
|
||||
$scope.addAzureEndpoint = function() {
|
||||
|
@ -79,14 +79,14 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
var tenantId = $scope.formValues.AzureTenantId;
|
||||
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
|
||||
var groupId = $scope.formValues.GroupId;
|
||||
var tags = $scope.formValues.Tags;
|
||||
var tagIds = $scope.formValues.TagIds;
|
||||
|
||||
createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags);
|
||||
createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds);
|
||||
};
|
||||
|
||||
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags) {
|
||||
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags)
|
||||
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
|
||||
.then(function success() {
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.go('portainer.endpoints', {}, {reload: true});
|
||||
|
@ -99,9 +99,9 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
});
|
||||
}
|
||||
|
||||
function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
function addEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Endpoint created', name);
|
||||
if (type === 4) {
|
||||
|
@ -121,7 +121,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
|||
function initView() {
|
||||
$q.all({
|
||||
groups: GroupService.groups(),
|
||||
tags: TagService.tagNames()
|
||||
tags: TagService.tags()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.groups = data.groups;
|
||||
|
|
|
@ -259,8 +259,9 @@
|
|||
<!-- tags -->
|
||||
<div class="form-group">
|
||||
<tag-selector
|
||||
ng-if="availableTags"
|
||||
tags="availableTags"
|
||||
model="formValues.Tags"
|
||||
model="formValues.TagIds"
|
||||
></tag-selector>
|
||||
</div>
|
||||
<!-- !tags -->
|
||||
|
|
|
@ -165,8 +165,9 @@
|
|||
<!-- tags -->
|
||||
<div class="form-group">
|
||||
<tag-selector
|
||||
ng-if="availableTags && endpoint.TagIds"
|
||||
tags="availableTags"
|
||||
model="endpoint.Tags"
|
||||
model="endpoint.TagIds"
|
||||
></tag-selector>
|
||||
</div>
|
||||
<!-- !tags -->
|
||||
|
|
|
@ -41,12 +41,12 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService,
|
|||
var TLSMode = securityData.TLSMode;
|
||||
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
|
||||
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
|
||||
|
||||
|
||||
var payload = {
|
||||
Name: endpoint.Name,
|
||||
PublicURL: endpoint.PublicURL,
|
||||
GroupID: endpoint.GroupId,
|
||||
Tags: endpoint.Tags,
|
||||
TagIds: endpoint.TagIds,
|
||||
TLS: TLS,
|
||||
TLSSkipVerify: TLSSkipVerify,
|
||||
TLSSkipClientVerify: TLSSkipClientVerify,
|
||||
|
@ -96,7 +96,7 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService,
|
|||
$q.all({
|
||||
endpoint: EndpointService.endpoint($transition$.params().id),
|
||||
groups: GroupService.groups(),
|
||||
tags: TagService.tagNames()
|
||||
tags: TagService.tags()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var endpoint = data.endpoint;
|
||||
|
|
|
@ -32,7 +32,7 @@ function ($q, $scope, $state, GroupService, EndpointService, TagService, Notific
|
|||
};
|
||||
|
||||
function initView() {
|
||||
TagService.tagNames()
|
||||
TagService.tags()
|
||||
.then((tags) => {
|
||||
$scope.availableTags = tags;
|
||||
$scope.associatedEndpoints = [];
|
||||
|
|
|
@ -28,7 +28,7 @@ function ($q, $scope, $state, $transition$, GroupService, TagService, Notificati
|
|||
|
||||
$q.all({
|
||||
group: GroupService.group(groupId),
|
||||
tags: TagService.tagNames()
|
||||
tags: TagService.tags()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.group = data.group;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<endpoint-list
|
||||
title-text="Endpoints" title-icon="fa-plug"
|
||||
endpoints="endpoints" table-key="home_endpoints"
|
||||
tags="tags"
|
||||
dashboard-action="goToDashboard"
|
||||
show-snapshot-action="!applicationState.application.authentication || isAdmin"
|
||||
snapshot-action="triggerSnapshot"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('HomeController', ['$q', '$scope', '$state', '$interval', 'Authentication', 'EndpointService', 'EndpointHelper', 'GroupService', 'Notifications', 'EndpointProvider', 'StateManager', 'LegacyExtensionManager', 'ModalService', 'MotdService', 'SystemService',
|
||||
function($q, $scope, $state, $interval, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, LegacyExtensionManager, ModalService, MotdService, SystemService) {
|
||||
.controller('HomeController',
|
||||
function($q, $scope, $state, TagService, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, LegacyExtensionManager, ModalService, MotdService, SystemService) {
|
||||
|
||||
$scope.state = {
|
||||
connectingToEdgeEndpoint: false,
|
||||
|
@ -165,9 +165,9 @@ angular.module('portainer.app')
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
function initView() {
|
||||
async function initView() {
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
|
||||
|
||||
MotdService.motd()
|
||||
.then(function success(data) {
|
||||
$scope.motd = data;
|
||||
|
@ -183,7 +183,13 @@ angular.module('portainer.app')
|
|||
$scope.endpoints = data.endpoints;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
$scope.tags = await TagService.tags();
|
||||
} catch(e) {
|
||||
Notifications.error("Failed loading tags", e)
|
||||
}
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
});
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import {ScheduleDefaultModel} from '../../../models/schedule';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('CreateScheduleController', ['$q', '$scope', '$state', 'Notifications', 'EndpointService', 'GroupService', 'ScheduleService',
|
||||
function ($q, $scope, $state, Notifications, EndpointService, GroupService, ScheduleService) {
|
||||
.controller('CreateScheduleController', function CreateScheduleController($q, $scope, $state, Notifications, EndpointService, GroupService, ScheduleService, TagService) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false
|
||||
|
@ -39,11 +38,13 @@ function ($q, $scope, $state, Notifications, EndpointService, GroupService, Sche
|
|||
|
||||
$q.all({
|
||||
endpoints: EndpointService.endpoints(),
|
||||
groups: GroupService.groups()
|
||||
groups: GroupService.groups(),
|
||||
tags: TagService.tags()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.endpoints = data.endpoints.value;
|
||||
$scope.groups = data.groups;
|
||||
$scope.tags = data.tags;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint list');
|
||||
|
@ -51,4 +52,4 @@ function ($q, $scope, $state, Notifications, EndpointService, GroupService, Sche
|
|||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
model="model"
|
||||
endpoints="endpoints"
|
||||
groups="groups"
|
||||
tags="tags"
|
||||
form-action="create"
|
||||
form-action-label="Create schedule"
|
||||
action-in-progress="state.actionInProgress"
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
model="schedule"
|
||||
endpoints="endpoints"
|
||||
groups="groups"
|
||||
tags="tags"
|
||||
form-action="update"
|
||||
form-action-label="Update schedule"
|
||||
action-in-progress="state.actionInProgress"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('ScheduleController', ['$q', '$scope', '$transition$', '$state', 'Notifications', 'EndpointService', 'GroupService', 'ScheduleService', 'EndpointProvider', 'HostBrowserService', 'FileSaver',
|
||||
function ($q, $scope, $transition$, $state, Notifications, EndpointService, GroupService, ScheduleService, EndpointProvider, HostBrowserService, FileSaver) {
|
||||
.controller('ScheduleController', function ScheduleController($q, $scope, $transition$, $state, Notifications, EndpointService, GroupService, ScheduleService, EndpointProvider, HostBrowserService, FileSaver, TagService) {
|
||||
|
||||
$scope.state = {
|
||||
actionInProgress: false
|
||||
|
@ -75,7 +74,8 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
|||
file: ScheduleService.getScriptFile(id),
|
||||
tasks: ScheduleService.scriptExecutionTasks(id),
|
||||
endpoints: EndpointService.endpoints(),
|
||||
groups: GroupService.groups()
|
||||
groups: GroupService.groups(),
|
||||
tags: TagService.tags()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var schedule = data.schedule;
|
||||
|
@ -89,6 +89,7 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
|||
$scope.tasks = data.tasks;
|
||||
$scope.endpoints = data.endpoints.value;
|
||||
$scope.groups = data.groups;
|
||||
$scope.tags = data.tags;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint list');
|
||||
|
@ -96,4 +97,4 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
|||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue