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
Chaim Lev-Ari 2020-03-29 12:54:14 +03:00 committed by GitHub
parent fe89a4fc01
commit edd86f2506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 404 additions and 171 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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{},
}

View File

@ -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)

View File

@ -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

View File

@ -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{},
}

View File

@ -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
}

View File

@ -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

View File

@ -37,6 +37,7 @@ type Handler struct {
JobService portainer.JobService
ReverseTunnelService portainer.ReverseTunnelService
SettingsService portainer.SettingsService
TagsService portainer.TagService
AuthorizationService *portainer.AuthorizationService
}

View File

@ -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)

View File

@ -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]
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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">

View File

@ -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,
});

View File

@ -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)
);

View File

@ -5,6 +5,7 @@ angular.module('portainer.app').component('endpointList', {
titleText: '@',
titleIcon: '@',
endpoints: '<',
tags: '<',
tableKey: '@',
dashboardAction: '<',
snapshotAction: '<',

View File

@ -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...

View File

@ -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 -->

View File

@ -69,6 +69,7 @@ angular.module('portainer.app').component('scheduleForm', {
model: '=',
endpoints: '<',
groups: '<',
tags: '<',
addLabelAction: '<',
removeLabelAction: '<',
formAction: '<',

View File

@ -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 -->

View File

@ -2,8 +2,9 @@ angular.module('portainer.app').component('multiEndpointSelector', {
templateUrl: './multiEndpointSelector.html',
controller: 'MultiEndpointSelectorController',
bindings: {
'model': '=',
'endpoints': '<',
'groups': '<'
}
model: '=',
endpoints: '<',
groups: '<',
tags: '<',
},
});

View File

@ -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>

View File

@ -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);

View File

@ -3,6 +3,6 @@ angular.module('portainer.app').component('tagSelector', {
controller: 'TagSelectorController',
bindings: {
tags: '<',
model: '='
}
model: '=',
},
});

View File

@ -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">

View File

@ -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;
});

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
})

View File

@ -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

View File

@ -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;

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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;

View File

@ -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 = [];

View File

@ -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;

View File

@ -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"

View File

@ -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();
}]);
});

View File

@ -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();
}]);
});

View File

@ -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"

View File

@ -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"

View File

@ -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();
}]);
});