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,
|
ScheduleService: store.ScheduleService,
|
||||||
SettingsService: store.SettingsService,
|
SettingsService: store.SettingsService,
|
||||||
StackService: store.StackService,
|
StackService: store.StackService,
|
||||||
|
TagService: store.TagService,
|
||||||
TeamMembershipService: store.TeamMembershipService,
|
TeamMembershipService: store.TeamMembershipService,
|
||||||
TemplateService: store.TemplateService,
|
TemplateService: store.TemplateService,
|
||||||
UserService: store.UserService,
|
UserService: store.UserService,
|
||||||
|
|
|
@ -16,7 +16,7 @@ func (store *Store) Init() error {
|
||||||
Labels: []portainer.Pair{},
|
Labels: []portainer.Pair{},
|
||||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||||
Tags: []string{},
|
TagIDs: []portainer.TagID{},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.EndpointGroupService.CreateEndpointGroup(unassignedGroup)
|
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/schedule"
|
||||||
"github.com/portainer/portainer/api/bolt/settings"
|
"github.com/portainer/portainer/api/bolt/settings"
|
||||||
"github.com/portainer/portainer/api/bolt/stack"
|
"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/teammembership"
|
||||||
"github.com/portainer/portainer/api/bolt/template"
|
"github.com/portainer/portainer/api/bolt/template"
|
||||||
"github.com/portainer/portainer/api/bolt/user"
|
"github.com/portainer/portainer/api/bolt/user"
|
||||||
|
@ -32,6 +33,7 @@ type (
|
||||||
scheduleService *schedule.Service
|
scheduleService *schedule.Service
|
||||||
settingsService *settings.Service
|
settingsService *settings.Service
|
||||||
stackService *stack.Service
|
stackService *stack.Service
|
||||||
|
tagService *tag.Service
|
||||||
teamMembershipService *teammembership.Service
|
teamMembershipService *teammembership.Service
|
||||||
templateService *template.Service
|
templateService *template.Service
|
||||||
userService *user.Service
|
userService *user.Service
|
||||||
|
@ -52,6 +54,7 @@ type (
|
||||||
ScheduleService *schedule.Service
|
ScheduleService *schedule.Service
|
||||||
SettingsService *settings.Service
|
SettingsService *settings.Service
|
||||||
StackService *stack.Service
|
StackService *stack.Service
|
||||||
|
TagService *tag.Service
|
||||||
TeamMembershipService *teammembership.Service
|
TeamMembershipService *teammembership.Service
|
||||||
TemplateService *template.Service
|
TemplateService *template.Service
|
||||||
UserService *user.Service
|
UserService *user.Service
|
||||||
|
@ -73,6 +76,7 @@ func NewMigrator(parameters *Parameters) *Migrator {
|
||||||
roleService: parameters.RoleService,
|
roleService: parameters.RoleService,
|
||||||
scheduleService: parameters.ScheduleService,
|
scheduleService: parameters.ScheduleService,
|
||||||
settingsService: parameters.SettingsService,
|
settingsService: parameters.SettingsService,
|
||||||
|
tagService: parameters.TagService,
|
||||||
teamMembershipService: parameters.TeamMembershipService,
|
teamMembershipService: parameters.TeamMembershipService,
|
||||||
templateService: parameters.TemplateService,
|
templateService: parameters.TemplateService,
|
||||||
stackService: parameters.StackService,
|
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)
|
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,19 @@ func (service *Service) Tags() ([]portainer.Tag, error) {
|
||||||
return tags, err
|
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.
|
// CreateTag creates a new tag.
|
||||||
func (service *Service) CreateTag(tag *portainer.Tag) error {
|
func (service *Service) CreateTag(tag *portainer.Tag) error {
|
||||||
return service.db.Update(func(tx *bolt.Tx) 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,
|
LogoURL: *flags.Logo,
|
||||||
AuthenticationMethod: portainer.AuthenticationInternal,
|
AuthenticationMethod: portainer.AuthenticationInternal,
|
||||||
LDAPSettings: portainer.LDAPSettings{
|
LDAPSettings: portainer.LDAPSettings{
|
||||||
AnonymousMode: true,
|
AnonymousMode: true,
|
||||||
AutoCreateUsers: true,
|
AutoCreateUsers: true,
|
||||||
TLSConfig: portainer.TLSConfiguration{},
|
TLSConfig: portainer.TLSConfiguration{},
|
||||||
SearchSettings: []portainer.LDAPSearchSettings{
|
SearchSettings: []portainer.LDAPSearchSettings{
|
||||||
|
@ -397,7 +397,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, endpointService portain
|
||||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||||
Extensions: []portainer.EndpointExtension{},
|
Extensions: []portainer.EndpointExtension{},
|
||||||
Tags: []string{},
|
TagIDs: []portainer.TagID{},
|
||||||
Status: portainer.EndpointStatusUp,
|
Status: portainer.EndpointStatusUp,
|
||||||
Snapshots: []portainer.Snapshot{},
|
Snapshots: []portainer.Snapshot{},
|
||||||
}
|
}
|
||||||
|
@ -440,7 +440,7 @@ func createUnsecuredEndpoint(endpointURL string, endpointService portainer.Endpo
|
||||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||||
Extensions: []portainer.EndpointExtension{},
|
Extensions: []portainer.EndpointExtension{},
|
||||||
Tags: []string{},
|
TagIDs: []portainer.TagID{},
|
||||||
Status: portainer.EndpointStatusUp,
|
Status: portainer.EndpointStatusUp,
|
||||||
Snapshots: []portainer.Snapshot{},
|
Snapshots: []portainer.Snapshot{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,15 @@ type endpointGroupCreatePayload struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
AssociatedEndpoints []portainer.EndpointID
|
AssociatedEndpoints []portainer.EndpointID
|
||||||
Tags []string
|
TagIDs []portainer.TagID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *endpointGroupCreatePayload) Validate(r *http.Request) error {
|
func (payload *endpointGroupCreatePayload) Validate(r *http.Request) error {
|
||||||
if govalidator.IsNull(payload.Name) {
|
if govalidator.IsNull(payload.Name) {
|
||||||
return portainer.Error("Invalid endpoint group name")
|
return portainer.Error("Invalid endpoint group name")
|
||||||
}
|
}
|
||||||
if payload.Tags == nil {
|
if payload.TagIDs == nil {
|
||||||
payload.Tags = []string{}
|
payload.TagIDs = []portainer.TagID{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func (handler *Handler) endpointGroupCreate(w http.ResponseWriter, r *http.Reque
|
||||||
Description: payload.Description,
|
Description: payload.Description,
|
||||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||||
Tags: payload.Tags,
|
TagIDs: payload.TagIDs,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.EndpointGroupService.CreateEndpointGroup(endpointGroup)
|
err = handler.EndpointGroupService.CreateEndpointGroup(endpointGroup)
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
type endpointGroupUpdatePayload struct {
|
type endpointGroupUpdatePayload struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Tags []string
|
TagIDs []portainer.TagID
|
||||||
UserAccessPolicies portainer.UserAccessPolicies
|
UserAccessPolicies portainer.UserAccessPolicies
|
||||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
TeamAccessPolicies portainer.TeamAccessPolicies
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,8 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
|
||||||
endpointGroup.Description = payload.Description
|
endpointGroup.Description = payload.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.Tags != nil {
|
if payload.TagIDs != nil {
|
||||||
endpointGroup.Tags = payload.Tags
|
endpointGroup.TagIDs = payload.TagIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAuthorizations := false
|
updateAuthorizations := false
|
||||||
|
|
|
@ -32,7 +32,7 @@ type endpointCreatePayload struct {
|
||||||
AzureApplicationID string
|
AzureApplicationID string
|
||||||
AzureTenantID string
|
AzureTenantID string
|
||||||
AzureAuthenticationKey string
|
AzureAuthenticationKey string
|
||||||
Tags []string
|
TagIDs []portainer.TagID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -54,14 +54,14 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||||
}
|
}
|
||||||
payload.GroupID = groupID
|
payload.GroupID = groupID
|
||||||
|
|
||||||
var tags []string
|
var tagIDs []portainer.TagID
|
||||||
err = request.RetrieveMultiPartFormJSONValue(r, "Tags", &tags, true)
|
err = request.RetrieveMultiPartFormJSONValue(r, "TagIds", &tagIDs, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return portainer.Error("Invalid Tags parameter")
|
return portainer.Error("Invalid TagIds parameter")
|
||||||
}
|
}
|
||||||
payload.Tags = tags
|
payload.TagIDs = tagIDs
|
||||||
if payload.Tags == nil {
|
if payload.TagIDs == nil {
|
||||||
payload.Tags = make([]string, 0)
|
payload.TagIDs = make([]portainer.TagID, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
useTLS, _ := request.RetrieveBooleanMultiPartFormValue(r, "TLS", true)
|
useTLS, _ := request.RetrieveBooleanMultiPartFormValue(r, "TLS", true)
|
||||||
|
@ -187,7 +187,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||||
Extensions: []portainer.EndpointExtension{},
|
Extensions: []portainer.EndpointExtension{},
|
||||||
AzureCredentials: credentials,
|
AzureCredentials: credentials,
|
||||||
Tags: payload.Tags,
|
TagIDs: payload.TagIDs,
|
||||||
Status: portainer.EndpointStatusUp,
|
Status: portainer.EndpointStatusUp,
|
||||||
Snapshots: []portainer.Snapshot{},
|
Snapshots: []portainer.Snapshot{},
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload)
|
||||||
AuthorizedUsers: []portainer.UserID{},
|
AuthorizedUsers: []portainer.UserID{},
|
||||||
AuthorizedTeams: []portainer.TeamID{},
|
AuthorizedTeams: []portainer.TeamID{},
|
||||||
Extensions: []portainer.EndpointExtension{},
|
Extensions: []portainer.EndpointExtension{},
|
||||||
Tags: payload.Tags,
|
TagIDs: payload.TagIDs,
|
||||||
Status: portainer.EndpointStatusUp,
|
Status: portainer.EndpointStatusUp,
|
||||||
Snapshots: []portainer.Snapshot{},
|
Snapshots: []portainer.Snapshot{},
|
||||||
EdgeKey: edgeKey,
|
EdgeKey: edgeKey,
|
||||||
|
@ -278,7 +278,7 @@ func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload)
|
||||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||||
Extensions: []portainer.EndpointExtension{},
|
Extensions: []portainer.EndpointExtension{},
|
||||||
Tags: payload.Tags,
|
TagIDs: payload.TagIDs,
|
||||||
Status: portainer.EndpointStatusUp,
|
Status: portainer.EndpointStatusUp,
|
||||||
Snapshots: []portainer.Snapshot{},
|
Snapshots: []portainer.Snapshot{},
|
||||||
}
|
}
|
||||||
|
@ -322,7 +322,7 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload)
|
||||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||||
Extensions: []portainer.EndpointExtension{},
|
Extensions: []portainer.EndpointExtension{},
|
||||||
Tags: payload.Tags,
|
TagIDs: payload.TagIDs,
|
||||||
Status: portainer.EndpointStatusUp,
|
Status: portainer.EndpointStatusUp,
|
||||||
Snapshots: []portainer.Snapshot{},
|
Snapshots: []portainer.Snapshot{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
"github.com/portainer/portainer/api"
|
||||||
|
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
|
|
||||||
|
@ -52,7 +52,15 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
||||||
}
|
}
|
||||||
|
|
||||||
if search != "" {
|
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 {
|
if endpointType != 0 {
|
||||||
|
@ -102,17 +110,17 @@ func filterEndpointsByGroupID(endpoints []portainer.Endpoint, endpointGroupID po
|
||||||
return filteredEndpoints
|
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)
|
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
|
endpointTags := convertTagIDsToTags(tagsMap, endpoint.TagIDs)
|
||||||
if endpointMatchSearchCriteria(&endpoint, searchCriteria) {
|
if endpointMatchSearchCriteria(&endpoint, endpointTags, searchCriteria) {
|
||||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, searchCriteria) {
|
if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, tagsMap, searchCriteria) {
|
||||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +128,7 @@ func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGro
|
||||||
return filteredEndpoints
|
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) {
|
if strings.Contains(strings.ToLower(endpoint.Name), searchCriteria) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -134,8 +142,7 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria st
|
||||||
} else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" {
|
} else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
for _, tag := range tags {
|
||||||
for _, tag := range endpoint.Tags {
|
|
||||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -144,14 +151,14 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria st
|
||||||
return false
|
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 {
|
for _, group := range endpointGroups {
|
||||||
if group.ID == endpoint.GroupID {
|
if group.ID == endpoint.GroupID {
|
||||||
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
|
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
tags := convertTagIDsToTags(tagsMap, group.TagIDs)
|
||||||
for _, tag := range group.Tags {
|
for _, tag := range tags {
|
||||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -172,3 +179,11 @@ func filterEndpointsByType(endpoints []portainer.Endpoint, endpointType portaine
|
||||||
}
|
}
|
||||||
return filteredEndpoints
|
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
|
AzureApplicationID *string
|
||||||
AzureTenantID *string
|
AzureTenantID *string
|
||||||
AzureAuthenticationKey *string
|
AzureAuthenticationKey *string
|
||||||
Tags []string
|
TagIDs []portainer.TagID
|
||||||
UserAccessPolicies portainer.UserAccessPolicies
|
UserAccessPolicies portainer.UserAccessPolicies
|
||||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
TeamAccessPolicies portainer.TeamAccessPolicies
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,8 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
endpoint.GroupID = portainer.EndpointGroupID(*payload.GroupID)
|
endpoint.GroupID = portainer.EndpointGroupID(*payload.GroupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.Tags != nil {
|
if payload.TagIDs != nil {
|
||||||
endpoint.Tags = payload.Tags
|
endpoint.TagIDs = payload.TagIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAuthorizations := false
|
updateAuthorizations := false
|
||||||
|
|
|
@ -37,6 +37,7 @@ type Handler struct {
|
||||||
JobService portainer.JobService
|
JobService portainer.JobService
|
||||||
ReverseTunnelService portainer.ReverseTunnelService
|
ReverseTunnelService portainer.ReverseTunnelService
|
||||||
SettingsService portainer.SettingsService
|
SettingsService portainer.SettingsService
|
||||||
|
TagsService portainer.TagService
|
||||||
AuthorizationService *portainer.AuthorizationService
|
AuthorizationService *portainer.AuthorizationService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,9 @@ import (
|
||||||
// Handler is the HTTP handler used to handle tag operations.
|
// Handler is the HTTP handler used to handle tag operations.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
TagService portainer.TagService
|
TagService portainer.TagService
|
||||||
|
EndpointService portainer.EndpointService
|
||||||
|
EndpointGroupService portainer.EndpointGroupService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a handler to manage tag operations.
|
// NewHandler creates a handler to manage tag operations.
|
||||||
|
@ -23,7 +25,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
h.Handle("/tags",
|
h.Handle("/tags",
|
||||||
bouncer.AdminAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
|
bouncer.AdminAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
|
||||||
h.Handle("/tags",
|
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}",
|
h.Handle("/tags/{id}",
|
||||||
bouncer.AdminAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete)
|
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 {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid tag identifier route variable", err}
|
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))
|
err = handler.TagService.DeleteTag(portainer.TagID(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -23,3 +56,21 @@ func (handler *Handler) tagDelete(w http.ResponseWriter, r *http.Request) *httpe
|
||||||
|
|
||||||
return response.Empty(w)
|
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/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/docker"
|
||||||
"github.com/portainer/portainer/api/http/handler"
|
"github.com/portainer/portainer/api/http/handler"
|
||||||
"github.com/portainer/portainer/api/http/handler/auth"
|
"github.com/portainer/portainer/api/http/handler/auth"
|
||||||
|
@ -222,6 +222,8 @@ func (server *Server) Start() error {
|
||||||
|
|
||||||
var tagHandler = tags.NewHandler(requestBouncer)
|
var tagHandler = tags.NewHandler(requestBouncer)
|
||||||
tagHandler.TagService = server.TagService
|
tagHandler.TagService = server.TagService
|
||||||
|
tagHandler.EndpointService = server.EndpointService
|
||||||
|
tagHandler.EndpointGroupService = server.EndpointGroupService
|
||||||
|
|
||||||
var teamHandler = teams.NewHandler(requestBouncer)
|
var teamHandler = teams.NewHandler(requestBouncer)
|
||||||
teamHandler.TeamService = server.TeamService
|
teamHandler.TeamService = server.TeamService
|
||||||
|
|
|
@ -260,7 +260,7 @@ type (
|
||||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||||
Extensions []EndpointExtension `json:"Extensions"`
|
Extensions []EndpointExtension `json:"Extensions"`
|
||||||
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
||||||
Tags []string `json:"Tags"`
|
TagIDs []TagID `json:"TagIds"`
|
||||||
Status EndpointStatus `json:"Status"`
|
Status EndpointStatus `json:"Status"`
|
||||||
Snapshots []Snapshot `json:"Snapshots"`
|
Snapshots []Snapshot `json:"Snapshots"`
|
||||||
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
||||||
|
@ -277,6 +277,9 @@ type (
|
||||||
// Deprecated in DBVersion == 18
|
// Deprecated in DBVersion == 18
|
||||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||||
|
|
||||||
|
// Deprecated in DBVersion == 22
|
||||||
|
Tags []string `json:"Tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorization represents an authorization associated to an operation
|
// Authorization represents an authorization associated to an operation
|
||||||
|
@ -426,7 +429,7 @@ type (
|
||||||
Description string `json:"Description"`
|
Description string `json:"Description"`
|
||||||
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
||||||
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
|
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
|
||||||
Tags []string `json:"Tags"`
|
TagIDs []TagID `json:"TagIds"`
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
Labels []Pair `json:"Labels"`
|
Labels []Pair `json:"Labels"`
|
||||||
|
@ -434,6 +437,9 @@ type (
|
||||||
// Deprecated in DBVersion == 18
|
// Deprecated in DBVersion == 18
|
||||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||||
|
|
||||||
|
// Deprecated in DBVersion == 22
|
||||||
|
Tags []string `json:"Tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndpointExtension represents a deprecated form of Portainer extension
|
// EndpointExtension represents a deprecated form of Portainer extension
|
||||||
|
@ -775,6 +781,7 @@ type (
|
||||||
// TagService represents a service for managing tag data
|
// TagService represents a service for managing tag data
|
||||||
TagService interface {
|
TagService interface {
|
||||||
Tags() ([]Tag, error)
|
Tags() ([]Tag, error)
|
||||||
|
Tag(ID TagID) (*Tag, error)
|
||||||
CreateTag(tag *Tag) error
|
CreateTag(tag *Tag) error
|
||||||
DeleteTag(ID TagID) error
|
DeleteTag(ID TagID) error
|
||||||
}
|
}
|
||||||
|
@ -919,7 +926,7 @@ const (
|
||||||
// APIVersion is the version number of the Portainer API
|
// APIVersion is the version number of the Portainer API
|
||||||
APIVersion = "1.24.0-dev"
|
APIVersion = "1.24.0-dev"
|
||||||
// DBVersion is the version number of the Portainer database
|
// DBVersion is the version number of the Portainer database
|
||||||
DBVersion = 22
|
DBVersion = 23
|
||||||
// AssetsServerURL represents the URL of the Portainer asset server
|
// AssetsServerURL represents the URL of the Portainer asset server
|
||||||
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
|
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
|
||||||
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved
|
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved
|
||||||
|
|
|
@ -1,12 +1,42 @@
|
||||||
angular.module('portainer.app').controller('EndpointItemController', [
|
import angular from 'angular';
|
||||||
function EndpointItemController() {
|
import _ from 'lodash-es';
|
||||||
var ctrl = this;
|
import PortainerEndpointTagHelper from 'Portainer/helpers/tagHelper';
|
||||||
|
|
||||||
ctrl.editEndpoint = editEndpoint;
|
class EndpointItemController {
|
||||||
|
/* @ngInject */
|
||||||
function editEndpoint(event) {
|
constructor() {
|
||||||
event.stopPropagation();
|
this.editEndpoint = this.editEndpoint.bind(this);
|
||||||
ctrl.onEdit(ctrl.model.Id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]);
|
|
||||||
|
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>
|
||||||
<span class="space-left space-right">-</span>
|
<span class="space-left space-right">-</span>
|
||||||
</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
|
<i class="fa fa-tags" aria-hidden="true"></i> No tags
|
||||||
</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>
|
<i class="fa fa-tags" aria-hidden="true"></i>
|
||||||
<span ng-repeat="tag in $ctrl.model.Tags">
|
{{ $ctrl.endpointTags }}
|
||||||
{{ tag }}{{ $last? '' : ', ' }}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="small text-muted" ng-if="$ctrl.model.Type !== 4">
|
<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', {
|
angular.module('portainer.app').component('endpointItem', {
|
||||||
templateUrl: './endpointItem.html',
|
templateUrl: './endpointItem.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
model: '<',
|
model: '<',
|
||||||
onSelect: '<',
|
onSelect: '<',
|
||||||
onEdit: '<',
|
onEdit: '<',
|
||||||
isAdmin:'<'
|
isAdmin: '<',
|
||||||
|
tags: '<',
|
||||||
},
|
},
|
||||||
controller: 'EndpointItemController'
|
controller: EndpointItemController,
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,12 +29,12 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
|
||||||
if (this.hasBackendPagination()) {
|
if (this.hasBackendPagination()) {
|
||||||
this.paginationChangedAction();
|
this.paginationChangedAction();
|
||||||
} else {
|
} else {
|
||||||
this.state.filteredEndpoints = frontEndpointFilter(this.endpoints, filterValue);
|
this.state.filteredEndpoints = frontEndpointFilter(this.endpoints, this.tags, filterValue);
|
||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function frontEndpointFilter(endpoints, filterValue) {
|
function frontEndpointFilter(endpoints, tags, filterValue) {
|
||||||
if (!endpoints || !endpoints.length || !filterValue) {
|
if (!endpoints || !endpoints.length || !filterValue) {
|
||||||
return endpoints;
|
return endpoints;
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,12 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
|
||||||
_.includes(endpoint.Name.toLowerCase(), lowerCaseKeyword) ||
|
_.includes(endpoint.Name.toLowerCase(), lowerCaseKeyword) ||
|
||||||
_.includes(endpoint.GroupName.toLowerCase(), lowerCaseKeyword) ||
|
_.includes(endpoint.GroupName.toLowerCase(), lowerCaseKeyword) ||
|
||||||
_.includes(endpoint.URL.toLowerCase(), lowerCaseKeyword) ||
|
_.includes(endpoint.URL.toLowerCase(), lowerCaseKeyword) ||
|
||||||
_.some(endpoint.Tags, function(tag) {
|
_.some(endpoint.TagIds, tagId => {
|
||||||
return _.includes(tag.toLowerCase(), lowerCaseKeyword);
|
const tag = tags.find(t => t.Id === tagId);
|
||||||
|
if (!tag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _.includes(tag.Name.toLowerCase(), lowerCaseKeyword);
|
||||||
}) ||
|
}) ||
|
||||||
_.includes(statusString, keyword)
|
_.includes(statusString, keyword)
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@ angular.module('portainer.app').component('endpointList', {
|
||||||
titleText: '@',
|
titleText: '@',
|
||||||
titleIcon: '@',
|
titleIcon: '@',
|
||||||
endpoints: '<',
|
endpoints: '<',
|
||||||
|
tags: '<',
|
||||||
tableKey: '@',
|
tableKey: '@',
|
||||||
dashboardAction: '<',
|
dashboardAction: '<',
|
||||||
snapshotAction: '<',
|
snapshotAction: '<',
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
on-select="$ctrl.dashboardAction"
|
on-select="$ctrl.dashboardAction"
|
||||||
on-edit="$ctrl.editAction"
|
on-edit="$ctrl.editAction"
|
||||||
is-admin="$ctrl.isAdmin"
|
is-admin="$ctrl.isAdmin"
|
||||||
|
tags="$ctrl.tags"
|
||||||
></endpoint-item>
|
></endpoint-item>
|
||||||
<endpoint-item ng-if="!$ctrl.hasBackendPagination()"
|
<endpoint-item ng-if="!$ctrl.hasBackendPagination()"
|
||||||
dir-paginate="endpoint in $ctrl.state.filteredEndpoints | itemsPerPage: $ctrl.state.paginatedItemLimit"
|
dir-paginate="endpoint in $ctrl.state.filteredEndpoints | itemsPerPage: $ctrl.state.paginatedItemLimit"
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
on-select="$ctrl.dashboardAction"
|
on-select="$ctrl.dashboardAction"
|
||||||
on-edit="$ctrl.editAction"
|
on-edit="$ctrl.editAction"
|
||||||
is-admin="$ctrl.isAdmin"
|
is-admin="$ctrl.isAdmin"
|
||||||
|
tags="$ctrl.tags"
|
||||||
></endpoint-item>
|
></endpoint-item>
|
||||||
<div ng-if="$ctrl.state.loading" class="text-center text-muted">
|
<div ng-if="$ctrl.state.loading" class="text-center text-muted">
|
||||||
Loading...
|
Loading...
|
||||||
|
|
|
@ -28,8 +28,9 @@
|
||||||
<!-- tags -->
|
<!-- tags -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<tag-selector
|
<tag-selector
|
||||||
|
ng-if="$ctrl.availableTags && $ctrl.model.TagIds"
|
||||||
tags="$ctrl.availableTags"
|
tags="$ctrl.availableTags"
|
||||||
model="$ctrl.model.Tags"
|
model="$ctrl.model.TagIds"
|
||||||
></tag-selector>
|
></tag-selector>
|
||||||
</div>
|
</div>
|
||||||
<!-- !tags -->
|
<!-- !tags -->
|
||||||
|
|
|
@ -69,6 +69,7 @@ angular.module('portainer.app').component('scheduleForm', {
|
||||||
model: '=',
|
model: '=',
|
||||||
endpoints: '<',
|
endpoints: '<',
|
||||||
groups: '<',
|
groups: '<',
|
||||||
|
tags: '<',
|
||||||
addLabelAction: '<',
|
addLabelAction: '<',
|
||||||
removeLabelAction: '<',
|
removeLabelAction: '<',
|
||||||
formAction: '<',
|
formAction: '<',
|
||||||
|
|
|
@ -265,9 +265,9 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- node-selection -->
|
<!-- node-selection -->
|
||||||
<multi-endpoint-selector
|
<multi-endpoint-selector
|
||||||
ng-if="$ctrl.endpoints && $ctrl.groups"
|
ng-if="$ctrl.endpoints && $ctrl.groups && $ctrl.tags"
|
||||||
model="$ctrl.model.Job.Endpoints"
|
model="$ctrl.model.Job.Endpoints"
|
||||||
endpoints="$ctrl.endpoints" groups="$ctrl.groups"
|
endpoints="$ctrl.endpoints" groups="$ctrl.groups" tags="$ctrl.tags"
|
||||||
></multi-endpoint-selector>
|
></multi-endpoint-selector>
|
||||||
<!-- !node-selection -->
|
<!-- !node-selection -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
|
|
|
@ -2,8 +2,9 @@ angular.module('portainer.app').component('multiEndpointSelector', {
|
||||||
templateUrl: './multiEndpointSelector.html',
|
templateUrl: './multiEndpointSelector.html',
|
||||||
controller: 'MultiEndpointSelectorController',
|
controller: 'MultiEndpointSelectorController',
|
||||||
bindings: {
|
bindings: {
|
||||||
'model': '=',
|
model: '=',
|
||||||
'endpoints': '<',
|
endpoints: '<',
|
||||||
'groups': '<'
|
groups: '<',
|
||||||
}
|
tags: '<',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
<ui-select-match placeholder="Select one or multiple endpoint(s)">
|
<ui-select-match placeholder="Select one or multiple endpoint(s)">
|
||||||
<span>
|
<span>
|
||||||
{{ $item.Name }}
|
{{ $item.Name }}
|
||||||
<span ng-if="$item.Tags.length">({{ $item.Tags | arraytostr }})</span>
|
<span ng-if="$item.TagIds.length">({{ $ctrl.tagIdsToTagNames($item.TagIds) | arraytostr }})</span>
|
||||||
</span>
|
</span>
|
||||||
</ui-select-match>
|
</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 }">
|
<ui-select-choices group-by="$ctrl.groupEndpoints" group-filter="$ctrl.sortGroups" repeat="endpoint.Id as endpoint in $ctrl.endpoints | filter: { Name: $select.search }">
|
||||||
<span>
|
<span>
|
||||||
{{ endpoint.Name }}
|
{{ endpoint.Name }}
|
||||||
<span ng-if="endpoint.Tags.length">({{ endpoint.Tags | arraytostr }})</span>
|
<span ng-if="endpoint.TagIds.length">({{ $ctrl.tagIdsToTagNames(endpoint.TagIds) | arraytostr }})</span>
|
||||||
</span>
|
</span>
|
||||||
</ui-select-choices>
|
</ui-select-choices>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import PortainerEndpointTagHelper from 'Portainer/helpers/tagHelper';
|
||||||
|
|
||||||
angular.module('portainer.app')
|
import angular from 'angular';
|
||||||
.controller('MultiEndpointSelectorController', function () {
|
|
||||||
var ctrl = this;
|
|
||||||
|
|
||||||
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']);
|
return _.sortBy(groups, ['name']);
|
||||||
};
|
}
|
||||||
|
|
||||||
this.groupEndpoints = function(endpoint) {
|
groupEndpoints(endpoint) {
|
||||||
for (var i = 0; i < ctrl.availableGroups.length; i++) {
|
for (var i = 0; i < this.availableGroups.length; i++) {
|
||||||
var group = ctrl.availableGroups[i];
|
var group = this.availableGroups[i];
|
||||||
|
|
||||||
if (endpoint.GroupId === group.Id) {
|
if (endpoint.GroupId === group.Id) {
|
||||||
return group.Name;
|
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',
|
controller: 'TagSelectorController',
|
||||||
bindings: {
|
bindings: {
|
||||||
tags: '<',
|
tags: '<',
|
||||||
model: '='
|
model: '=',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
Selected tags
|
Selected tags
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-9 col-lg-10" style="padding-top: 4px;">
|
<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)">
|
<span class="tag space-right interactive" ng-repeat="tag in $ctrl.state.selectedTags" ng-click="$ctrl.removeTag(tag)">
|
||||||
{{ tag }}
|
{{ tag.Name }}
|
||||||
<a title="Remove tag" ng-click="$ctrl.removeTag(tag)" style="margin-left: 2px;">
|
<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>
|
<span class="fa fa-trash-alt white-icon" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -20,10 +20,11 @@
|
||||||
type="text" ng-model="$ctrl.state.selectedValue"
|
type="text" ng-model="$ctrl.state.selectedValue"
|
||||||
id="tags" class="form-control"
|
id="tags" class="form-control"
|
||||||
placeholder="Select tags..."
|
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-on-select="$ctrl.selectTag($item, $model, $label)"
|
||||||
typeahead-no-results="$ctrl.state.noResult"
|
typeahead-no-results="$ctrl.state.noResult"
|
||||||
typeahead-show-hint="true" typeahead-min-length="0" />
|
typeahead-show-hint="true" typeahead-min-length="0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-9 col-lg-10" ng-if="$ctrl.tags.length === 0">
|
<div class="col-sm-9 col-lg-10" ng-if="$ctrl.tags.length === 0">
|
||||||
<span class="small text-muted">
|
<span class="small text-muted">
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app').controller('TagSelectorController', function() {
|
||||||
.controller('TagSelectorController', function () {
|
this.$onInit = function() {
|
||||||
|
this.state.selectedTags = _.map(this.model, (id) => _.find(this.tags, (t) => t.Id === id));
|
||||||
this.$onChanges = function(changes) {
|
|
||||||
if(angular.isDefined(changes.tags.currentValue)) {
|
|
||||||
this.tags = _.difference(changes.tags.currentValue, this.model);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedValue: '',
|
selectedValue: '',
|
||||||
noResult: false
|
selectedTags: [],
|
||||||
|
noResult: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.selectTag = function($item) {
|
this.selectTag = function($item) {
|
||||||
this.state.selectedValue = '';
|
this.state.selectedValue = '';
|
||||||
this.model.push($item);
|
this.model.push($item.Id);
|
||||||
this.tags = _.remove(this.tags, function(item) {
|
this.state.selectedTags.push($item);
|
||||||
return item !== $item;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.removeTag = function(tag) {
|
this.removeTag = function removeTag(tag) {
|
||||||
var idx = this.model.indexOf(tag);
|
_.remove(this.state.selectedTags, { Id: tag.Id });
|
||||||
if (idx > -1) {
|
_.remove(this.model, (id) => id === tag.Id);
|
||||||
this.model.splice(idx, 1);
|
|
||||||
this.tags.push(tag);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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() {
|
export function EndpointGroupDefaultModel() {
|
||||||
this.Name = '';
|
this.Name = '';
|
||||||
this.Description = '';
|
this.Description = '';
|
||||||
this.Tags = [];
|
this.TagIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EndpointGroupModel(data) {
|
export function EndpointGroupModel(data) {
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
this.Name = data.Name;
|
this.Name = data.Name;
|
||||||
this.Description = data.Description;
|
this.Description = data.Description;
|
||||||
this.Tags = data.Tags;
|
this.TagIds = data.TagIds;
|
||||||
this.AuthorizedUsers = data.AuthorizedUsers;
|
this.AuthorizedUsers = data.AuthorizedUsers;
|
||||||
this.AuthorizedTeams = data.AuthorizedTeams;
|
this.AuthorizedTeams = data.AuthorizedTeams;
|
||||||
this.UserAccessPolicies = data.UserAccessPolicies;
|
this.UserAccessPolicies = data.UserAccessPolicies;
|
||||||
|
@ -18,7 +18,7 @@ export function EndpointGroupModel(data) {
|
||||||
export function EndpointGroupCreateRequest(model, endpoints) {
|
export function EndpointGroupCreateRequest(model, endpoints) {
|
||||||
this.Name = model.Name;
|
this.Name = model.Name;
|
||||||
this.Description = model.Description;
|
this.Description = model.Description;
|
||||||
this.Tags = model.Tags;
|
this.TagIds = model.TagIds;
|
||||||
this.AssociatedEndpoints = endpoints;
|
this.AssociatedEndpoints = endpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export function EndpointGroupUpdateRequest(model, endpoints) {
|
||||||
this.id = model.Id;
|
this.id = model.Id;
|
||||||
this.Name = model.Name;
|
this.Name = model.Name;
|
||||||
this.Description = model.Description;
|
this.Description = model.Description;
|
||||||
this.Tags = model.Tags;
|
this.TagIds = model.TagIds;
|
||||||
this.AssociatedEndpoints = endpoints;
|
this.AssociatedEndpoints = endpoints;
|
||||||
this.UserAccessPolicies = model.UserAccessPolicies;
|
this.UserAccessPolicies = model.UserAccessPolicies;
|
||||||
this.TeamAccessPolicies = model.TeamAccessPolicies;
|
this.TeamAccessPolicies = model.TeamAccessPolicies;
|
||||||
|
|
|
@ -63,7 +63,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
||||||
return deferred.promise;
|
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 deferred = $q.defer();
|
||||||
|
|
||||||
var endpointURL = URL;
|
var endpointURL = URL;
|
||||||
|
@ -71,7 +71,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
||||||
endpointURL = 'tcp://' + URL;
|
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) {
|
.then(function success(response) {
|
||||||
deferred.resolve(response.data);
|
deferred.resolve(response.data);
|
||||||
})
|
})
|
||||||
|
@ -82,10 +82,10 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
|
||||||
return deferred.promise;
|
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();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags)
|
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
|
||||||
.then(function success(response) {
|
.then(function success(response) {
|
||||||
deferred.resolve(response.data);
|
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({
|
return Upload.upload({
|
||||||
url: 'api/endpoints',
|
url: 'api/endpoints',
|
||||||
data: {
|
data: {
|
||||||
|
@ -109,7 +109,7 @@ angular.module('portainer.app')
|
||||||
URL: URL,
|
URL: URL,
|
||||||
PublicURL: PublicURL,
|
PublicURL: PublicURL,
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
Tags: Upload.json(tags),
|
TagIds: Upload.json(tagIds),
|
||||||
TLS: TLS,
|
TLS: TLS,
|
||||||
TLSSkipVerify: TLSSkipVerify,
|
TLSSkipVerify: TLSSkipVerify,
|
||||||
TLSSkipClientVerify: TLSSkipClientVerify,
|
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({
|
return Upload.upload({
|
||||||
url: 'api/endpoints',
|
url: 'api/endpoints',
|
||||||
data: {
|
data: {
|
||||||
Name: name,
|
Name: name,
|
||||||
EndpointType: 3,
|
EndpointType: 3,
|
||||||
GroupID: groupId,
|
GroupID: groupId,
|
||||||
Tags: Upload.json(tags),
|
TagIds: Upload.json(tagIds),
|
||||||
AzureApplicationID: applicationId,
|
AzureApplicationID: applicationId,
|
||||||
AzureTenantID: tenantId,
|
AzureTenantID: tenantId,
|
||||||
AzureAuthenticationKey: authenticationKey
|
AzureAuthenticationKey: authenticationKey
|
||||||
|
|
|
@ -18,7 +18,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||||
AzureApplicationId: '',
|
AzureApplicationId: '',
|
||||||
AzureTenantId: '',
|
AzureTenantId: '',
|
||||||
AzureAuthenticationKey: '',
|
AzureAuthenticationKey: '',
|
||||||
Tags: []
|
TagIds: []
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.copyAgentCommand = function() {
|
$scope.copyAgentCommand = function() {
|
||||||
|
@ -40,7 +40,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||||
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
||||||
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
|
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
|
||||||
var groupId = $scope.formValues.GroupId;
|
var groupId = $scope.formValues.GroupId;
|
||||||
var tags = $scope.formValues.Tags;
|
var tagIds = $scope.formValues.TagIds;
|
||||||
|
|
||||||
var securityData = $scope.formValues.SecurityFormData;
|
var securityData = $scope.formValues.SecurityFormData;
|
||||||
var TLS = securityData.TLS;
|
var TLS = securityData.TLS;
|
||||||
|
@ -51,7 +51,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||||
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
|
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
|
||||||
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
|
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() {
|
$scope.addAgentEndpoint = function() {
|
||||||
|
@ -59,18 +59,18 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||||
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
var URL = $filter('stripprotocol')($scope.formValues.URL);
|
||||||
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
|
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
|
||||||
var groupId = $scope.formValues.GroupId;
|
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() {
|
$scope.addEdgeAgentEndpoint = function() {
|
||||||
var name = $scope.formValues.Name;
|
var name = $scope.formValues.Name;
|
||||||
var groupId = $scope.formValues.GroupId;
|
var groupId = $scope.formValues.GroupId;
|
||||||
var tags = $scope.formValues.Tags;
|
var tagIds = $scope.formValues.TagIds;
|
||||||
var URL = $scope.formValues.URL;
|
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() {
|
$scope.addAzureEndpoint = function() {
|
||||||
|
@ -79,14 +79,14 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||||
var tenantId = $scope.formValues.AzureTenantId;
|
var tenantId = $scope.formValues.AzureTenantId;
|
||||||
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
|
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
|
||||||
var groupId = $scope.formValues.GroupId;
|
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;
|
$scope.state.actionInProgress = true;
|
||||||
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags)
|
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
Notifications.success('Endpoint created', name);
|
Notifications.success('Endpoint created', name);
|
||||||
$state.go('portainer.endpoints', {}, {reload: true});
|
$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;
|
$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) {
|
.then(function success(data) {
|
||||||
Notifications.success('Endpoint created', name);
|
Notifications.success('Endpoint created', name);
|
||||||
if (type === 4) {
|
if (type === 4) {
|
||||||
|
@ -121,7 +121,7 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||||
function initView() {
|
function initView() {
|
||||||
$q.all({
|
$q.all({
|
||||||
groups: GroupService.groups(),
|
groups: GroupService.groups(),
|
||||||
tags: TagService.tagNames()
|
tags: TagService.tags()
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
$scope.groups = data.groups;
|
$scope.groups = data.groups;
|
||||||
|
|
|
@ -259,8 +259,9 @@
|
||||||
<!-- tags -->
|
<!-- tags -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<tag-selector
|
<tag-selector
|
||||||
|
ng-if="availableTags"
|
||||||
tags="availableTags"
|
tags="availableTags"
|
||||||
model="formValues.Tags"
|
model="formValues.TagIds"
|
||||||
></tag-selector>
|
></tag-selector>
|
||||||
</div>
|
</div>
|
||||||
<!-- !tags -->
|
<!-- !tags -->
|
||||||
|
|
|
@ -165,8 +165,9 @@
|
||||||
<!-- tags -->
|
<!-- tags -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<tag-selector
|
<tag-selector
|
||||||
|
ng-if="availableTags && endpoint.TagIds"
|
||||||
tags="availableTags"
|
tags="availableTags"
|
||||||
model="endpoint.Tags"
|
model="endpoint.TagIds"
|
||||||
></tag-selector>
|
></tag-selector>
|
||||||
</div>
|
</div>
|
||||||
<!-- !tags -->
|
<!-- !tags -->
|
||||||
|
|
|
@ -41,12 +41,12 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService,
|
||||||
var TLSMode = securityData.TLSMode;
|
var TLSMode = securityData.TLSMode;
|
||||||
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
|
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
|
||||||
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
|
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
Name: endpoint.Name,
|
Name: endpoint.Name,
|
||||||
PublicURL: endpoint.PublicURL,
|
PublicURL: endpoint.PublicURL,
|
||||||
GroupID: endpoint.GroupId,
|
GroupID: endpoint.GroupId,
|
||||||
Tags: endpoint.Tags,
|
TagIds: endpoint.TagIds,
|
||||||
TLS: TLS,
|
TLS: TLS,
|
||||||
TLSSkipVerify: TLSSkipVerify,
|
TLSSkipVerify: TLSSkipVerify,
|
||||||
TLSSkipClientVerify: TLSSkipClientVerify,
|
TLSSkipClientVerify: TLSSkipClientVerify,
|
||||||
|
@ -96,7 +96,7 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService,
|
||||||
$q.all({
|
$q.all({
|
||||||
endpoint: EndpointService.endpoint($transition$.params().id),
|
endpoint: EndpointService.endpoint($transition$.params().id),
|
||||||
groups: GroupService.groups(),
|
groups: GroupService.groups(),
|
||||||
tags: TagService.tagNames()
|
tags: TagService.tags()
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var endpoint = data.endpoint;
|
var endpoint = data.endpoint;
|
||||||
|
|
|
@ -32,7 +32,7 @@ function ($q, $scope, $state, GroupService, EndpointService, TagService, Notific
|
||||||
};
|
};
|
||||||
|
|
||||||
function initView() {
|
function initView() {
|
||||||
TagService.tagNames()
|
TagService.tags()
|
||||||
.then((tags) => {
|
.then((tags) => {
|
||||||
$scope.availableTags = tags;
|
$scope.availableTags = tags;
|
||||||
$scope.associatedEndpoints = [];
|
$scope.associatedEndpoints = [];
|
||||||
|
|
|
@ -28,7 +28,7 @@ function ($q, $scope, $state, $transition$, GroupService, TagService, Notificati
|
||||||
|
|
||||||
$q.all({
|
$q.all({
|
||||||
group: GroupService.group(groupId),
|
group: GroupService.group(groupId),
|
||||||
tags: TagService.tagNames()
|
tags: TagService.tags()
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
$scope.group = data.group;
|
$scope.group = data.group;
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
<endpoint-list
|
<endpoint-list
|
||||||
title-text="Endpoints" title-icon="fa-plug"
|
title-text="Endpoints" title-icon="fa-plug"
|
||||||
endpoints="endpoints" table-key="home_endpoints"
|
endpoints="endpoints" table-key="home_endpoints"
|
||||||
|
tags="tags"
|
||||||
dashboard-action="goToDashboard"
|
dashboard-action="goToDashboard"
|
||||||
show-snapshot-action="!applicationState.application.authentication || isAdmin"
|
show-snapshot-action="!applicationState.application.authentication || isAdmin"
|
||||||
snapshot-action="triggerSnapshot"
|
snapshot-action="triggerSnapshot"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.controller('HomeController', ['$q', '$scope', '$state', '$interval', 'Authentication', 'EndpointService', 'EndpointHelper', 'GroupService', 'Notifications', 'EndpointProvider', 'StateManager', 'LegacyExtensionManager', 'ModalService', 'MotdService', 'SystemService',
|
.controller('HomeController',
|
||||||
function($q, $scope, $state, $interval, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, LegacyExtensionManager, ModalService, MotdService, SystemService) {
|
function($q, $scope, $state, TagService, Authentication, EndpointService, EndpointHelper, GroupService, Notifications, EndpointProvider, StateManager, LegacyExtensionManager, ModalService, MotdService, SystemService) {
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
connectingToEdgeEndpoint: false,
|
connectingToEdgeEndpoint: false,
|
||||||
|
@ -165,9 +165,9 @@ angular.module('portainer.app')
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initView() {
|
async function initView() {
|
||||||
$scope.isAdmin = Authentication.isAdmin();
|
$scope.isAdmin = Authentication.isAdmin();
|
||||||
|
|
||||||
MotdService.motd()
|
MotdService.motd()
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
$scope.motd = data;
|
$scope.motd = data;
|
||||||
|
@ -183,7 +183,13 @@ angular.module('portainer.app')
|
||||||
$scope.endpoints = data.endpoints;
|
$scope.endpoints = data.endpoints;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
$scope.tags = await TagService.tags();
|
||||||
|
} catch(e) {
|
||||||
|
Notifications.error("Failed loading tags", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
}]);
|
});
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import {ScheduleDefaultModel} from '../../../models/schedule';
|
import {ScheduleDefaultModel} from '../../../models/schedule';
|
||||||
|
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.controller('CreateScheduleController', ['$q', '$scope', '$state', 'Notifications', 'EndpointService', 'GroupService', 'ScheduleService',
|
.controller('CreateScheduleController', function CreateScheduleController($q, $scope, $state, Notifications, EndpointService, GroupService, ScheduleService, TagService) {
|
||||||
function ($q, $scope, $state, Notifications, EndpointService, GroupService, ScheduleService) {
|
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
actionInProgress: false
|
actionInProgress: false
|
||||||
|
@ -39,11 +38,13 @@ function ($q, $scope, $state, Notifications, EndpointService, GroupService, Sche
|
||||||
|
|
||||||
$q.all({
|
$q.all({
|
||||||
endpoints: EndpointService.endpoints(),
|
endpoints: EndpointService.endpoints(),
|
||||||
groups: GroupService.groups()
|
groups: GroupService.groups(),
|
||||||
|
tags: TagService.tags()
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
$scope.endpoints = data.endpoints.value;
|
$scope.endpoints = data.endpoints.value;
|
||||||
$scope.groups = data.groups;
|
$scope.groups = data.groups;
|
||||||
|
$scope.tags = data.tags;
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint list');
|
Notifications.error('Failure', err, 'Unable to retrieve endpoint list');
|
||||||
|
@ -51,4 +52,4 @@ function ($q, $scope, $state, Notifications, EndpointService, GroupService, Sche
|
||||||
}
|
}
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
}]);
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
model="model"
|
model="model"
|
||||||
endpoints="endpoints"
|
endpoints="endpoints"
|
||||||
groups="groups"
|
groups="groups"
|
||||||
|
tags="tags"
|
||||||
form-action="create"
|
form-action="create"
|
||||||
form-action-label="Create schedule"
|
form-action-label="Create schedule"
|
||||||
action-in-progress="state.actionInProgress"
|
action-in-progress="state.actionInProgress"
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
model="schedule"
|
model="schedule"
|
||||||
endpoints="endpoints"
|
endpoints="endpoints"
|
||||||
groups="groups"
|
groups="groups"
|
||||||
|
tags="tags"
|
||||||
form-action="update"
|
form-action="update"
|
||||||
form-action-label="Update schedule"
|
form-action-label="Update schedule"
|
||||||
action-in-progress="state.actionInProgress"
|
action-in-progress="state.actionInProgress"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
angular.module('portainer.app')
|
angular.module('portainer.app')
|
||||||
.controller('ScheduleController', ['$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) {
|
||||||
function ($q, $scope, $transition$, $state, Notifications, EndpointService, GroupService, ScheduleService, EndpointProvider, HostBrowserService, FileSaver) {
|
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
actionInProgress: false
|
actionInProgress: false
|
||||||
|
@ -75,7 +74,8 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
||||||
file: ScheduleService.getScriptFile(id),
|
file: ScheduleService.getScriptFile(id),
|
||||||
tasks: ScheduleService.scriptExecutionTasks(id),
|
tasks: ScheduleService.scriptExecutionTasks(id),
|
||||||
endpoints: EndpointService.endpoints(),
|
endpoints: EndpointService.endpoints(),
|
||||||
groups: GroupService.groups()
|
groups: GroupService.groups(),
|
||||||
|
tags: TagService.tags()
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var schedule = data.schedule;
|
var schedule = data.schedule;
|
||||||
|
@ -89,6 +89,7 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
||||||
$scope.tasks = data.tasks;
|
$scope.tasks = data.tasks;
|
||||||
$scope.endpoints = data.endpoints.value;
|
$scope.endpoints = data.endpoints.value;
|
||||||
$scope.groups = data.groups;
|
$scope.groups = data.groups;
|
||||||
|
$scope.tags = data.tags;
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint list');
|
Notifications.error('Failure', err, 'Unable to retrieve endpoint list');
|
||||||
|
@ -96,4 +97,4 @@ function ($q, $scope, $transition$, $state, Notifications, EndpointService, Grou
|
||||||
}
|
}
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
}]);
|
});
|
||||||
|
|
Loading…
Reference in New Issue