1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-25 00:09:40 +02:00

feat(tags): add the ability to manage tags (#1971)

* feat(tags): add the ability to manage tags

* feat(tags): update tag selector UX

* refactor(app): remove unused ui-select library
This commit is contained in:
Anthony Lapenna 2018-06-15 09:18:25 +02:00 committed by GitHub
parent b349f16090
commit 5e73a49473
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 942 additions and 118 deletions

View file

@ -13,14 +13,17 @@ import (
type endpointGroupCreatePayload struct {
Name string
Description string
Labels []portainer.Pair
AssociatedEndpoints []portainer.EndpointID
Tags []string
}
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{}
}
return nil
}
@ -35,9 +38,9 @@ func (handler *Handler) endpointGroupCreate(w http.ResponseWriter, r *http.Reque
endpointGroup := &portainer.EndpointGroup{
Name: payload.Name,
Description: payload.Description,
Labels: payload.Labels,
AuthorizedUsers: []portainer.UserID{},
AuthorizedTeams: []portainer.TeamID{},
Tags: payload.Tags,
}
err = handler.EndpointGroupService.CreateEndpointGroup(endpointGroup)

View file

@ -12,8 +12,8 @@ import (
type endpointGroupUpdatePayload struct {
Name string
Description string
Labels []portainer.Pair
AssociatedEndpoints []portainer.EndpointID
Tags []string
}
func (payload *endpointGroupUpdatePayload) Validate(r *http.Request) error {
@ -48,7 +48,9 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
endpointGroup.Description = payload.Description
}
endpointGroup.Labels = payload.Labels
if payload.Tags != nil {
endpointGroup.Tags = payload.Tags
}
err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, endpointGroup)
if err != nil {

View file

@ -28,6 +28,7 @@ type endpointCreatePayload struct {
AzureApplicationID string
AzureTenantID string
AzureAuthenticationKey string
Tags []string
}
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
@ -49,6 +50,13 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
}
payload.GroupID = groupID
var tags []string
err = request.RetrieveMultiPartFormJSONValue(r, "Tags", &tags, true)
if err != nil {
return portainer.Error("Invalid Tags parameter")
}
payload.Tags = tags
useTLS, _ := request.RetrieveBooleanMultiPartFormValue(r, "TLS", true)
payload.TLS = useTLS
@ -168,6 +176,7 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
AuthorizedTeams: []portainer.TeamID{},
Extensions: []portainer.EndpointExtension{},
AzureCredentials: credentials,
Tags: payload.Tags,
}
err = handler.EndpointService.CreateEndpoint(endpoint)
@ -203,6 +212,7 @@ func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload)
AuthorizedUsers: []portainer.UserID{},
AuthorizedTeams: []portainer.TeamID{},
Extensions: []portainer.EndpointExtension{},
Tags: payload.Tags,
}
err := handler.EndpointService.CreateEndpoint(endpoint)
@ -242,6 +252,7 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload)
AuthorizedUsers: []portainer.UserID{},
AuthorizedTeams: []portainer.TeamID{},
Extensions: []portainer.EndpointExtension{},
Tags: payload.Tags,
}
err = handler.EndpointService.CreateEndpoint(endpoint)

View file

@ -22,6 +22,7 @@ type endpointUpdatePayload struct {
AzureApplicationID string
AzureTenantID string
AzureAuthenticationKey string
Tags []string
}
func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
@ -68,6 +69,10 @@ 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 endpoint.Type == portainer.AzureEnvironment {
credentials := endpoint.AzureCredentials
if payload.AzureApplicationID != "" {

View file

@ -15,6 +15,7 @@ import (
"github.com/portainer/portainer/http/handler/settings"
"github.com/portainer/portainer/http/handler/stacks"
"github.com/portainer/portainer/http/handler/status"
"github.com/portainer/portainer/http/handler/tags"
"github.com/portainer/portainer/http/handler/teammemberships"
"github.com/portainer/portainer/http/handler/teams"
"github.com/portainer/portainer/http/handler/templates"
@ -37,16 +38,13 @@ type Handler struct {
SettingsHandler *settings.Handler
StackHandler *stacks.Handler
StatusHandler *status.Handler
TagHandler *tags.Handler
TeamMembershipHandler *teammemberships.Handler
TeamHandler *teams.Handler
TemplatesHandler *templates.Handler
UploadHandler *upload.Handler
UserHandler *users.Handler
WebSocketHandler *websocket.Handler
// StoridgeHandler *extensions.StoridgeHandler
// AzureHandler *azure.Handler
// DockerHandler *docker.Handler
}
// ServeHTTP delegates a request to the appropriate subhandler.
@ -79,6 +77,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/api", h.StackHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/status"):
http.StripPrefix("/api", h.StatusHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/tags"):
http.StripPrefix("/api", h.TagHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/templates"):
http.StripPrefix("/api", h.TemplatesHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/upload"):

View file

@ -0,0 +1,31 @@
package tags
import (
"net/http"
"github.com/gorilla/mux"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/security"
)
// Handler is the HTTP handler used to handle tag operations.
type Handler struct {
*mux.Router
TagService portainer.TagService
}
// NewHandler creates a handler to manage tag operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
}
h.Handle("/tags",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
h.Handle("/tags",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet)
h.Handle("/tags/{id}",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete)
return h
}

View file

@ -0,0 +1,53 @@
package tags
import (
"net/http"
"github.com/asaskevich/govalidator"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
type tagCreatePayload struct {
Name string
}
func (payload *tagCreatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Name) {
return portainer.Error("Invalid tag name")
}
return nil
}
// POST request on /api/tags
func (handler *Handler) tagCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload tagCreatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
tags, err := handler.TagService.Tags()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve tags from the database", err}
}
for _, tag := range tags {
if tag.Name == payload.Name {
return &httperror.HandlerError{http.StatusConflict, "This name is already associated to a tag", portainer.ErrTagAlreadyExists}
}
}
tag := &portainer.Tag{
Name: payload.Name,
}
err = handler.TagService.CreateTag(tag)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the tag inside the database", err}
}
return response.JSON(w, tag)
}

View file

@ -0,0 +1,25 @@
package tags
import (
"net/http"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
)
// DELETE request on /api/tags/:name
func (handler *Handler) tagDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
id, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid tag identifier route variable", err}
}
err = handler.TagService.DeleteTag(portainer.TagID(id))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the tag from the database", err}
}
return response.Empty(w)
}

View file

@ -0,0 +1,18 @@
package tags
import (
"net/http"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/response"
)
// GET request on /api/tags
func (handler *Handler) tagList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
tags, err := handler.TagService.Tags()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve tags from the database", err}
}
return response.JSON(w, tags)
}

View file

@ -16,6 +16,7 @@ import (
"github.com/portainer/portainer/http/handler/settings"
"github.com/portainer/portainer/http/handler/stacks"
"github.com/portainer/portainer/http/handler/status"
"github.com/portainer/portainer/http/handler/tags"
"github.com/portainer/portainer/http/handler/teammemberships"
"github.com/portainer/portainer/http/handler/teams"
"github.com/portainer/portainer/http/handler/templates"
@ -36,24 +37,25 @@ type Server struct {
AuthDisabled bool
EndpointManagement bool
Status *portainer.Status
UserService portainer.UserService
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
DockerHubService portainer.DockerHubService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
FileService portainer.FileService
GitService portainer.GitService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
RegistryService portainer.RegistryService
ResourceControlService portainer.ResourceControlService
SettingsService portainer.SettingsService
CryptoService portainer.CryptoService
JWTService portainer.JWTService
FileService portainer.FileService
RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService
StackService portainer.StackService
SwarmStackManager portainer.SwarmStackManager
ComposeStackManager portainer.ComposeStackManager
LDAPService portainer.LDAPService
GitService portainer.GitService
SignatureService portainer.DigitalSignatureService
TagService portainer.TagService
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
UserService portainer.UserService
Handler *handler.Handler
SSL bool
SSLCert string
@ -126,6 +128,9 @@ func (server *Server) Start() error {
stackHandler.RegistryService = server.RegistryService
stackHandler.DockerHubService = server.DockerHubService
var tagHandler = tags.NewHandler(requestBouncer)
tagHandler.TagService = server.TagService
var teamHandler = teams.NewHandler(requestBouncer)
teamHandler.TeamService = server.TeamService
teamHandler.TeamMembershipService = server.TeamMembershipService
@ -164,6 +169,7 @@ func (server *Server) Start() error {
SettingsHandler: settingsHandler,
StatusHandler: statusHandler,
StackHandler: stackHandler,
TagHandler: tagHandler,
TeamHandler: teamHandler,
TeamMembershipHandler: teamMembershipHandler,
TemplatesHandler: templatesHandler,