mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-04-29 22:51:42 +02:00
Merge pull request #379 from ClusterCockpit/add_tag_delete
Add Tag Deletion: API and Frontend
This commit is contained in:
commit
04692e0c44
@ -277,6 +277,7 @@ type Mutation {
|
|||||||
deleteTag(id: ID!): ID!
|
deleteTag(id: ID!): ID!
|
||||||
addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
||||||
removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
||||||
|
removeTagFromList(tagIds: [ID!]!): [Int!]!
|
||||||
|
|
||||||
updateConfiguration(name: String!, value: String!): String
|
updateConfiguration(name: String!, value: String!): String
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -1,6 +1,7 @@
|
|||||||
module github.com/ClusterCockpit/cc-backend
|
module github.com/ClusterCockpit/cc-backend
|
||||||
|
|
||||||
go 1.23.5
|
go 1.23.5
|
||||||
|
|
||||||
toolchain go1.24.1
|
toolchain go1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -78,12 +78,14 @@ func (api *RestApi) MountApiRoutes(r *mux.Router) {
|
|||||||
r.HandleFunc("/jobs/{id}", api.getJobById).Methods(http.MethodPost)
|
r.HandleFunc("/jobs/{id}", api.getJobById).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/jobs/{id}", api.getCompleteJobById).Methods(http.MethodGet)
|
r.HandleFunc("/jobs/{id}", api.getCompleteJobById).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch)
|
r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch)
|
||||||
|
r.HandleFunc("/jobs/tag_job/{id}", api.removeTagJob).Methods(http.MethodDelete)
|
||||||
r.HandleFunc("/jobs/edit_meta/{id}", api.editMeta).Methods(http.MethodPost, http.MethodPatch)
|
r.HandleFunc("/jobs/edit_meta/{id}", api.editMeta).Methods(http.MethodPost, http.MethodPatch)
|
||||||
r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet)
|
r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete)
|
r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete)
|
||||||
r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).Methods(http.MethodDelete)
|
r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).Methods(http.MethodDelete)
|
||||||
r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete)
|
r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete)
|
||||||
|
|
||||||
|
r.HandleFunc("/tags/", api.removeTags).Methods(http.MethodDelete)
|
||||||
r.HandleFunc("/clusters/", api.getClusters).Methods(http.MethodGet)
|
r.HandleFunc("/clusters/", api.getClusters).Methods(http.MethodGet)
|
||||||
|
|
||||||
if api.MachineStateDir != "" {
|
if api.MachineStateDir != "" {
|
||||||
@ -750,6 +752,114 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(rw).Encode(job)
|
json.NewEncoder(rw).Encode(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeTagJob godoc
|
||||||
|
// @summary Removes one or more tags from a job
|
||||||
|
// @tags Job add and modify
|
||||||
|
// @description Removes tag(s) from a job specified by DB ID. Name and Type of Tag(s) must match.
|
||||||
|
// @description Tag Scope is required for matching, options: "global", "admin". Private tags can not be deleted via API.
|
||||||
|
// @description If tagged job is already finished: Tag will be removed from respective archive files.
|
||||||
|
// @accept json
|
||||||
|
// @produce json
|
||||||
|
// @param id path int true "Job Database ID"
|
||||||
|
// @param request body api.TagJobApiRequest true "Array of tag-objects to remove"
|
||||||
|
// @success 200 {object} schema.Job "Updated job resource"
|
||||||
|
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
||||||
|
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
||||||
|
// @failure 404 {object} api.ErrorResponse "Job or tag does not exist"
|
||||||
|
// @failure 500 {object} api.ErrorResponse "Internal Server Error"
|
||||||
|
// @security ApiKeyAuth
|
||||||
|
// @router /jobs/tag_job/{id} [delete]
|
||||||
|
func (api *RestApi) removeTagJob(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job, err := api.JobRepository.FindById(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job.Tags, err = api.JobRepository.GetTags(repository.GetUserFromContext(r.Context()), &job.ID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req TagJobApiRequest
|
||||||
|
if err := decode(r.Body, &req); err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rtag := range req {
|
||||||
|
// Only Global and Admin Tags
|
||||||
|
if rtag.Scope != "global" && rtag.Scope != "admin" {
|
||||||
|
log.Warnf("Cannot delete private tag for job %d: Skip", job.JobID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingTags, err := api.JobRepository.RemoveJobTagByRequest(repository.GetUserFromContext(r.Context()), job.ID, rtag.Type, rtag.Name, rtag.Scope)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job.Tags = remainingTags
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Add("Content-Type", "application/json")
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(rw).Encode(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeTags godoc
|
||||||
|
// @summary Removes all tags and job-relations for type:name tuple
|
||||||
|
// @tags Tag remove
|
||||||
|
// @description Removes tags by type and name. Name and Type of Tag(s) must match.
|
||||||
|
// @description Tag Scope is required for matching, options: "global", "admin". Private tags can not be deleted via API.
|
||||||
|
// @description Tag wills be removed from respective archive files.
|
||||||
|
// @accept json
|
||||||
|
// @produce plain
|
||||||
|
// @param request body api.TagJobApiRequest true "Array of tag-objects to remove"
|
||||||
|
// @success 200 {string} string "Success Response"
|
||||||
|
// @failure 400 {object} api.ErrorResponse "Bad Request"
|
||||||
|
// @failure 401 {object} api.ErrorResponse "Unauthorized"
|
||||||
|
// @failure 404 {object} api.ErrorResponse "Job or tag does not exist"
|
||||||
|
// @failure 500 {object} api.ErrorResponse "Internal Server Error"
|
||||||
|
// @security ApiKeyAuth
|
||||||
|
// @router /tags/ [delete]
|
||||||
|
func (api *RestApi) removeTags(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
var req TagJobApiRequest
|
||||||
|
if err := decode(r.Body, &req); err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetCount := len(req)
|
||||||
|
currentCount := 0
|
||||||
|
for _, rtag := range req {
|
||||||
|
// Only Global and Admin Tags
|
||||||
|
if rtag.Scope != "global" && rtag.Scope != "admin" {
|
||||||
|
log.Warn("Cannot delete private tag: Skip")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := api.JobRepository.RemoveTagByRequest(rtag.Type, rtag.Name, rtag.Scope)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
currentCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
rw.Write([]byte(fmt.Sprintf("Deleted Tags from DB: %d successfull of %d requested\n", currentCount, targetCount)))
|
||||||
|
}
|
||||||
|
|
||||||
// startJob godoc
|
// startJob godoc
|
||||||
// @summary Adds a new job as "running"
|
// @summary Adds a new job as "running"
|
||||||
// @tags Job add and modify
|
// @tags Job add and modify
|
||||||
|
@ -250,6 +250,7 @@ type ComplexityRoot struct {
|
|||||||
AddTagsToJob func(childComplexity int, job string, tagIds []string) int
|
AddTagsToJob func(childComplexity int, job string, tagIds []string) int
|
||||||
CreateTag func(childComplexity int, typeArg string, name string, scope string) int
|
CreateTag func(childComplexity int, typeArg string, name string, scope string) int
|
||||||
DeleteTag func(childComplexity int, id string) int
|
DeleteTag func(childComplexity int, id string) int
|
||||||
|
RemoveTagFromList func(childComplexity int, tagIds []string) int
|
||||||
RemoveTagsFromJob func(childComplexity int, job string, tagIds []string) int
|
RemoveTagsFromJob func(childComplexity int, job string, tagIds []string) int
|
||||||
UpdateConfiguration func(childComplexity int, name string, value string) int
|
UpdateConfiguration func(childComplexity int, name string, value string) int
|
||||||
}
|
}
|
||||||
@ -399,6 +400,7 @@ type MutationResolver interface {
|
|||||||
DeleteTag(ctx context.Context, id string) (string, error)
|
DeleteTag(ctx context.Context, id string) (string, error)
|
||||||
AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error)
|
AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error)
|
||||||
RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error)
|
RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error)
|
||||||
|
RemoveTagFromList(ctx context.Context, tagIds []string) ([]int, error)
|
||||||
UpdateConfiguration(ctx context.Context, name string, value string) (*string, error)
|
UpdateConfiguration(ctx context.Context, name string, value string) (*string, error)
|
||||||
}
|
}
|
||||||
type QueryResolver interface {
|
type QueryResolver interface {
|
||||||
@ -1310,6 +1312,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Mutation.DeleteTag(childComplexity, args["id"].(string)), true
|
return e.complexity.Mutation.DeleteTag(childComplexity, args["id"].(string)), true
|
||||||
|
|
||||||
|
case "Mutation.removeTagFromList":
|
||||||
|
if e.complexity.Mutation.RemoveTagFromList == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err := ec.field_Mutation_removeTagFromList_args(context.TODO(), rawArgs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Mutation.RemoveTagFromList(childComplexity, args["tagIds"].([]string)), true
|
||||||
|
|
||||||
case "Mutation.removeTagsFromJob":
|
case "Mutation.removeTagsFromJob":
|
||||||
if e.complexity.Mutation.RemoveTagsFromJob == nil {
|
if e.complexity.Mutation.RemoveTagsFromJob == nil {
|
||||||
break
|
break
|
||||||
@ -2339,6 +2353,7 @@ type Mutation {
|
|||||||
deleteTag(id: ID!): ID!
|
deleteTag(id: ID!): ID!
|
||||||
addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
addTagsToJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
||||||
removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
removeTagsFromJob(job: ID!, tagIds: [ID!]!): [Tag!]!
|
||||||
|
removeTagFromList(tagIds: [ID!]!): [Int!]!
|
||||||
|
|
||||||
updateConfiguration(name: String!, value: String!): String
|
updateConfiguration(name: String!, value: String!): String
|
||||||
}
|
}
|
||||||
@ -2617,6 +2632,34 @@ func (ec *executionContext) field_Mutation_deleteTag_argsID(
|
|||||||
return zeroVal, nil
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) field_Mutation_removeTagFromList_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||||
|
var err error
|
||||||
|
args := map[string]any{}
|
||||||
|
arg0, err := ec.field_Mutation_removeTagFromList_argsTagIds(ctx, rawArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args["tagIds"] = arg0
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
func (ec *executionContext) field_Mutation_removeTagFromList_argsTagIds(
|
||||||
|
ctx context.Context,
|
||||||
|
rawArgs map[string]any,
|
||||||
|
) ([]string, error) {
|
||||||
|
if _, ok := rawArgs["tagIds"]; !ok {
|
||||||
|
var zeroVal []string
|
||||||
|
return zeroVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("tagIds"))
|
||||||
|
if tmp, ok := rawArgs["tagIds"]; ok {
|
||||||
|
return ec.unmarshalNID2ᚕstringᚄ(ctx, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroVal []string
|
||||||
|
return zeroVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) field_Mutation_removeTagsFromJob_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
func (ec *executionContext) field_Mutation_removeTagsFromJob_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||||
var err error
|
var err error
|
||||||
args := map[string]any{}
|
args := map[string]any{}
|
||||||
@ -9690,6 +9733,61 @@ func (ec *executionContext) fieldContext_Mutation_removeTagsFromJob(ctx context.
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Mutation_removeTagFromList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_Mutation_removeTagFromList(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return ec.resolvers.Mutation().RemoveTagFromList(rctx, fc.Args["tagIds"].([]string))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.([]int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNInt2ᚕintᚄ(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_Mutation_removeTagFromList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "Mutation",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: true,
|
||||||
|
IsResolver: true,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return nil, errors.New("field of type Int does not have child fields")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = ec.Recover(ctx, r)
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
if fc.Args, err = ec.field_Mutation_removeTagFromList_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return fc, err
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Mutation_updateConfiguration(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Mutation_updateConfiguration(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext_Mutation_updateConfiguration(ctx, field)
|
fc, err := ec.fieldContext_Mutation_updateConfiguration(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -17765,6 +17863,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
|
case "removeTagFromList":
|
||||||
|
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||||
|
return ec._Mutation_removeTagFromList(ctx, field)
|
||||||
|
})
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
out.Invalids++
|
||||||
|
}
|
||||||
case "updateConfiguration":
|
case "updateConfiguration":
|
||||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||||
return ec._Mutation_updateConfiguration(ctx, field)
|
return ec._Mutation_updateConfiguration(ctx, field)
|
||||||
|
@ -125,23 +125,41 @@ func (r *metricValueResolver) Name(ctx context.Context, obj *schema.MetricValue)
|
|||||||
|
|
||||||
// CreateTag is the resolver for the createTag field.
|
// CreateTag is the resolver for the createTag field.
|
||||||
func (r *mutationResolver) CreateTag(ctx context.Context, typeArg string, name string, scope string) (*schema.Tag, error) {
|
func (r *mutationResolver) CreateTag(ctx context.Context, typeArg string, name string, scope string) (*schema.Tag, error) {
|
||||||
id, err := r.Repo.CreateTag(typeArg, name, scope)
|
user := repository.GetUserFromContext(ctx)
|
||||||
if err != nil {
|
if user == nil {
|
||||||
log.Warn("Error while creating tag")
|
return nil, fmt.Errorf("no user in context")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &schema.Tag{ID: id, Type: typeArg, Name: name, Scope: scope}, nil
|
// Test Access: Admins && Admin Tag OR Support/Admin and Global Tag OR Everyone && Private Tag
|
||||||
|
if user.HasRole(schema.RoleAdmin) && scope == "admin" ||
|
||||||
|
user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) && scope == "global" ||
|
||||||
|
user.Username == scope {
|
||||||
|
// Create in DB
|
||||||
|
id, err := r.Repo.CreateTag(typeArg, name, scope)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while creating tag")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &schema.Tag{ID: id, Type: typeArg, Name: name, Scope: scope}, nil
|
||||||
|
} else {
|
||||||
|
log.Warnf("Not authorized to create tag with scope: %s", scope)
|
||||||
|
return nil, fmt.Errorf("Not authorized to create tag with scope: %s", scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTag is the resolver for the deleteTag field.
|
// DeleteTag is the resolver for the deleteTag field.
|
||||||
func (r *mutationResolver) DeleteTag(ctx context.Context, id string) (string, error) {
|
func (r *mutationResolver) DeleteTag(ctx context.Context, id string) (string, error) {
|
||||||
|
// This Uses ID string <-> ID string, removeTagFromList uses []string <-> []int
|
||||||
panic(fmt.Errorf("not implemented: DeleteTag - deleteTag"))
|
panic(fmt.Errorf("not implemented: DeleteTag - deleteTag"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTagsToJob is the resolver for the addTagsToJob field.
|
// AddTagsToJob is the resolver for the addTagsToJob field.
|
||||||
func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) {
|
func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) {
|
||||||
// Selectable Tags Pre-Filtered by Scope in Frontend: No backend check required
|
user := repository.GetUserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return nil, fmt.Errorf("no user in context")
|
||||||
|
}
|
||||||
|
|
||||||
jid, err := strconv.ParseInt(job, 10, 64)
|
jid, err := strconv.ParseInt(job, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while adding tag to job")
|
log.Warn("Error while adding tag to job")
|
||||||
@ -150,15 +168,32 @@ func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds
|
|||||||
|
|
||||||
tags := []*schema.Tag{}
|
tags := []*schema.Tag{}
|
||||||
for _, tagId := range tagIds {
|
for _, tagId := range tagIds {
|
||||||
|
// Get ID
|
||||||
tid, err := strconv.ParseInt(tagId, 10, 64)
|
tid, err := strconv.ParseInt(tagId, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while parsing tag id")
|
log.Warn("Error while parsing tag id")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tags, err = r.Repo.AddTag(repository.GetUserFromContext(ctx), jid, tid); err != nil {
|
// Test Exists
|
||||||
log.Warn("Error while adding tag")
|
_, _, tscope, exists := r.Repo.TagInfo(tid)
|
||||||
return nil, err
|
if !exists {
|
||||||
|
log.Warnf("Tag does not exist (ID): %d", tid)
|
||||||
|
return nil, fmt.Errorf("Tag does not exist (ID): %d", tid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Access: Admins && Admin Tag OR Support/Admin and Global Tag OR Everyone && Private Tag
|
||||||
|
if user.HasRole(schema.RoleAdmin) && tscope == "admin" ||
|
||||||
|
user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) && tscope == "global" ||
|
||||||
|
user.Username == tscope {
|
||||||
|
// Add to Job
|
||||||
|
if tags, err = r.Repo.AddTag(user, jid, tid); err != nil {
|
||||||
|
log.Warn("Error while adding tag")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnf("Not authorized to add tag: %d", tid)
|
||||||
|
return nil, fmt.Errorf("Not authorized to add tag: %d", tid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +202,11 @@ func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds
|
|||||||
|
|
||||||
// RemoveTagsFromJob is the resolver for the removeTagsFromJob field.
|
// RemoveTagsFromJob is the resolver for the removeTagsFromJob field.
|
||||||
func (r *mutationResolver) RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) {
|
func (r *mutationResolver) RemoveTagsFromJob(ctx context.Context, job string, tagIds []string) ([]*schema.Tag, error) {
|
||||||
// Removable Tags Pre-Filtered by Scope in Frontend: No backend check required
|
user := repository.GetUserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return nil, fmt.Errorf("no user in context")
|
||||||
|
}
|
||||||
|
|
||||||
jid, err := strconv.ParseInt(job, 10, 64)
|
jid, err := strconv.ParseInt(job, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while parsing job id")
|
log.Warn("Error while parsing job id")
|
||||||
@ -176,21 +215,80 @@ func (r *mutationResolver) RemoveTagsFromJob(ctx context.Context, job string, ta
|
|||||||
|
|
||||||
tags := []*schema.Tag{}
|
tags := []*schema.Tag{}
|
||||||
for _, tagId := range tagIds {
|
for _, tagId := range tagIds {
|
||||||
|
// Get ID
|
||||||
tid, err := strconv.ParseInt(tagId, 10, 64)
|
tid, err := strconv.ParseInt(tagId, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while parsing tag id")
|
log.Warn("Error while parsing tag id")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tags, err = r.Repo.RemoveTag(repository.GetUserFromContext(ctx), jid, tid); err != nil {
|
// Test Exists
|
||||||
log.Warn("Error while removing tag")
|
_, _, tscope, exists := r.Repo.TagInfo(tid)
|
||||||
return nil, err
|
if !exists {
|
||||||
|
log.Warnf("Tag does not exist (ID): %d", tid)
|
||||||
|
return nil, fmt.Errorf("Tag does not exist (ID): %d", tid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test Access: Admins && Admin Tag OR Support/Admin and Global Tag OR Everyone && Private Tag
|
||||||
|
if user.HasRole(schema.RoleAdmin) && tscope == "admin" ||
|
||||||
|
user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) && tscope == "global" ||
|
||||||
|
user.Username == tscope {
|
||||||
|
// Remove from Job
|
||||||
|
if tags, err = r.Repo.RemoveTag(user, jid, tid); err != nil {
|
||||||
|
log.Warn("Error while removing tag")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnf("Not authorized to remove tag: %d", tid)
|
||||||
|
return nil, fmt.Errorf("Not authorized to remove tag: %d", tid)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveTagFromList is the resolver for the removeTagFromList field.
|
||||||
|
func (r *mutationResolver) RemoveTagFromList(ctx context.Context, tagIds []string) ([]int, error) {
|
||||||
|
// Needs Contextuser
|
||||||
|
user := repository.GetUserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return nil, fmt.Errorf("no user in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := []int{}
|
||||||
|
for _, tagId := range tagIds {
|
||||||
|
// Get ID
|
||||||
|
tid, err := strconv.ParseInt(tagId, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while parsing tag id for removal")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Exists
|
||||||
|
_, _, tscope, exists := r.Repo.TagInfo(tid)
|
||||||
|
if !exists {
|
||||||
|
log.Warnf("Tag does not exist (ID): %d", tid)
|
||||||
|
return nil, fmt.Errorf("Tag does not exist (ID): %d", tid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Access: Admins && Admin Tag OR Everyone && Private Tag
|
||||||
|
if user.HasRole(schema.RoleAdmin) && (tscope == "global" || tscope == "admin") || user.Username == tscope {
|
||||||
|
// Remove from DB
|
||||||
|
if err = r.Repo.RemoveTagById(tid); err != nil {
|
||||||
|
log.Warn("Error while removing tag")
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
tags = append(tags, int(tid))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnf("Not authorized to remove tag: %d", tid)
|
||||||
|
return nil, fmt.Errorf("Not authorized to remove tag: %d", tid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateConfiguration is the resolver for the updateConfiguration field.
|
// UpdateConfiguration is the resolver for the updateConfiguration field.
|
||||||
func (r *mutationResolver) UpdateConfiguration(ctx context.Context, name string, value string) (*string, error) {
|
func (r *mutationResolver) UpdateConfiguration(ctx context.Context, name string, value string) (*string, error) {
|
||||||
if err := repository.GetUserCfgRepo().UpdateConfig(name, value, repository.GetUserFromContext(ctx)); err != nil {
|
if err := repository.GetUserCfgRepo().UpdateConfig(name, value, repository.GetUserFromContext(ctx)); err != nil {
|
||||||
|
@ -45,7 +45,7 @@ func (r *JobRepository) AddTag(user *schema.User, job int64, tag int64) ([]*sche
|
|||||||
return tags, archive.UpdateTags(j, archiveTags)
|
return tags, archive.UpdateTags(j, archiveTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes a tag from a job
|
// Removes a tag from a job by tag id
|
||||||
func (r *JobRepository) RemoveTag(user *schema.User, job, tag int64) ([]*schema.Tag, error) {
|
func (r *JobRepository) RemoveTag(user *schema.User, job, tag int64) ([]*schema.Tag, error) {
|
||||||
j, err := r.FindByIdWithUser(user, job)
|
j, err := r.FindByIdWithUser(user, job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,6 +76,99 @@ func (r *JobRepository) RemoveTag(user *schema.User, job, tag int64) ([]*schema.
|
|||||||
return tags, archive.UpdateTags(j, archiveTags)
|
return tags, archive.UpdateTags(j, archiveTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes a tag from a job by tag info
|
||||||
|
func (r *JobRepository) RemoveJobTagByRequest(user *schema.User, job int64, tagType string, tagName string, tagScope string) ([]*schema.Tag, error) {
|
||||||
|
// Get Tag ID to delete
|
||||||
|
tagID, exists := r.TagId(tagType, tagName, tagScope)
|
||||||
|
if !exists {
|
||||||
|
log.Warnf("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
|
||||||
|
return nil, fmt.Errorf("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Job
|
||||||
|
j, err := r.FindByIdWithUser(user, job)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while finding job by id")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Delete
|
||||||
|
q := sq.Delete("jobtag").Where("jobtag.job_id = ?", job).Where("jobtag.tag_id = ?", tagID)
|
||||||
|
|
||||||
|
if _, err := q.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
|
s, _, _ := q.ToSql()
|
||||||
|
log.Errorf("Error removing tag from table 'jobTag' with %s: %v", s, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := r.GetTags(user, &job)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while getting tags for job")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
archiveTags, err := r.getArchiveTags(&job)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while getting tags for job")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, archive.UpdateTags(j, archiveTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a tag from db by tag info
|
||||||
|
func (r *JobRepository) RemoveTagByRequest(tagType string, tagName string, tagScope string) error {
|
||||||
|
// Get Tag ID to delete
|
||||||
|
tagID, exists := r.TagId(tagType, tagName, tagScope)
|
||||||
|
if !exists {
|
||||||
|
log.Warnf("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
|
||||||
|
return fmt.Errorf("Tag does not exist (name, type, scope): %s, %s, %s", tagName, tagType, tagScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Delete JobTagTable
|
||||||
|
qJobTag := sq.Delete("jobtag").Where("jobtag.tag_id = ?", tagID)
|
||||||
|
|
||||||
|
if _, err := qJobTag.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
|
s, _, _ := qJobTag.ToSql()
|
||||||
|
log.Errorf("Error removing tag from table 'jobTag' with %s: %v", s, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Delete TagTable
|
||||||
|
qTag := sq.Delete("tag").Where("tag.id = ?", tagID)
|
||||||
|
|
||||||
|
if _, err := qTag.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
|
s, _, _ := qTag.ToSql()
|
||||||
|
log.Errorf("Error removing tag from table 'tag' with %s: %v", s, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a tag from db by tag id
|
||||||
|
func (r *JobRepository) RemoveTagById(tagID int64) error {
|
||||||
|
// Handle Delete JobTagTable
|
||||||
|
qJobTag := sq.Delete("jobtag").Where("jobtag.tag_id = ?", tagID)
|
||||||
|
|
||||||
|
if _, err := qJobTag.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
|
s, _, _ := qJobTag.ToSql()
|
||||||
|
log.Errorf("Error removing tag from table 'jobTag' with %s: %v", s, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Delete TagTable
|
||||||
|
qTag := sq.Delete("tag").Where("tag.id = ?", tagID)
|
||||||
|
|
||||||
|
if _, err := qTag.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
|
s, _, _ := qTag.ToSql()
|
||||||
|
log.Errorf("Error removing tag from table 'tag' with %s: %v", s, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateTag creates a new tag with the specified type and name and returns its database id.
|
// CreateTag creates a new tag with the specified type and name and returns its database id.
|
||||||
func (r *JobRepository) CreateTag(tagType string, tagName string, tagScope string) (tagId int64, err error) {
|
func (r *JobRepository) CreateTag(tagType string, tagName string, tagScope string) (tagId int64, err error) {
|
||||||
// Default to "Global" scope if none defined
|
// Default to "Global" scope if none defined
|
||||||
@ -209,6 +302,16 @@ func (r *JobRepository) TagId(tagType string, tagName string, tagScope string) (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagInfo returns the database infos of the tag with the specified id.
|
||||||
|
func (r *JobRepository) TagInfo(tagId int64) (tagType string, tagName string, tagScope string, exists bool) {
|
||||||
|
exists = true
|
||||||
|
if err := sq.Select("tag.tag_type", "tag.tag_name", "tag.tag_scope").From("tag").Where("tag.id = ?", tagId).
|
||||||
|
RunWith(r.stmtCache).QueryRow().Scan(&tagType, &tagName, &tagScope); err != nil {
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetTags returns a list of all scoped tags if job is nil or of the tags that the job with that database ID has.
|
// GetTags returns a list of all scoped tags if job is nil or of the tags that the job with that database ID has.
|
||||||
func (r *JobRepository) GetTags(user *schema.User, job *int64) ([]*schema.Tag, error) {
|
func (r *JobRepository) GetTags(user *schema.User, job *int64) ([]*schema.Tag, error) {
|
||||||
q := sq.Select("id", "tag_type", "tag_name", "tag_scope").From("tag")
|
q := sq.Select("id", "tag_type", "tag_name", "tag_scope").From("tag")
|
||||||
|
@ -85,6 +85,7 @@ func IsValidRole(role string) bool {
|
|||||||
return getRoleEnum(role) != RoleError
|
return getRoleEnum(role) != RoleError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if User has SPECIFIED role AND role is VALID
|
||||||
func (u *User) HasValidRole(role string) (hasRole bool, isValid bool) {
|
func (u *User) HasValidRole(role string) (hasRole bool, isValid bool) {
|
||||||
if IsValidRole(role) {
|
if IsValidRole(role) {
|
||||||
for _, r := range u.Roles {
|
for _, r := range u.Roles {
|
||||||
@ -97,6 +98,7 @@ func (u *User) HasValidRole(role string) (hasRole bool, isValid bool) {
|
|||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if User has SPECIFIED role
|
||||||
func (u *User) HasRole(role Role) bool {
|
func (u *User) HasRole(role Role) bool {
|
||||||
for _, r := range u.Roles {
|
for _, r := range u.Roles {
|
||||||
if r == GetRoleString(role) {
|
if r == GetRoleString(role) {
|
||||||
@ -106,7 +108,7 @@ func (u *User) HasRole(role Role) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Role-Arrays are short: performance not impacted by nested loop
|
// Check if User has ANY of the listed roles
|
||||||
func (u *User) HasAnyRole(queryroles []Role) bool {
|
func (u *User) HasAnyRole(queryroles []Role) bool {
|
||||||
for _, ur := range u.Roles {
|
for _, ur := range u.Roles {
|
||||||
for _, qr := range queryroles {
|
for _, qr := range queryroles {
|
||||||
@ -118,7 +120,7 @@ func (u *User) HasAnyRole(queryroles []Role) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Role-Arrays are short: performance not impacted by nested loop
|
// Check if User has ALL of the listed roles
|
||||||
func (u *User) HasAllRoles(queryroles []Role) bool {
|
func (u *User) HasAllRoles(queryroles []Role) bool {
|
||||||
target := len(queryroles)
|
target := len(queryroles)
|
||||||
matches := 0
|
matches := 0
|
||||||
@ -138,7 +140,7 @@ func (u *User) HasAllRoles(queryroles []Role) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Role-Arrays are short: performance not impacted by nested loop
|
// Check if User has NONE of the listed roles
|
||||||
func (u *User) HasNotRoles(queryroles []Role) bool {
|
func (u *User) HasNotRoles(queryroles []Role) bool {
|
||||||
matches := 0
|
matches := 0
|
||||||
for _, ur := range u.Roles {
|
for _, ur := range u.Roles {
|
||||||
|
@ -62,6 +62,7 @@ export default [
|
|||||||
entrypoint('jobs', 'src/jobs.entrypoint.js'),
|
entrypoint('jobs', 'src/jobs.entrypoint.js'),
|
||||||
entrypoint('user', 'src/user.entrypoint.js'),
|
entrypoint('user', 'src/user.entrypoint.js'),
|
||||||
entrypoint('list', 'src/list.entrypoint.js'),
|
entrypoint('list', 'src/list.entrypoint.js'),
|
||||||
|
entrypoint('taglist', 'src/tags.entrypoint.js'),
|
||||||
entrypoint('job', 'src/job.entrypoint.js'),
|
entrypoint('job', 'src/job.entrypoint.js'),
|
||||||
entrypoint('systems', 'src/systems.entrypoint.js'),
|
entrypoint('systems', 'src/systems.entrypoint.js'),
|
||||||
entrypoint('node', 'src/node.entrypoint.js'),
|
entrypoint('node', 'src/node.entrypoint.js'),
|
||||||
|
110
web/frontend/src/Tags.root.svelte
Normal file
110
web/frontend/src/Tags.root.svelte
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<!--
|
||||||
|
@component Tag List Svelte Component. Displays All Tags, Allows deletion.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
- `username String!`: Users username.
|
||||||
|
- `isAdmin Bool!`: User has Admin Auth.
|
||||||
|
- `tagmap Object!`: Map of accessible, appwide tags. Prefiltered in backend.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
gql,
|
||||||
|
getContextClient,
|
||||||
|
mutationStore,
|
||||||
|
} from "@urql/svelte";
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
InputGroup,
|
||||||
|
Icon,
|
||||||
|
Button,
|
||||||
|
Spinner,
|
||||||
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
import {
|
||||||
|
init,
|
||||||
|
} from "./generic/utils.js";
|
||||||
|
|
||||||
|
export let username;
|
||||||
|
export let isAdmin;
|
||||||
|
export let tagmap;
|
||||||
|
|
||||||
|
const {} = init();
|
||||||
|
const client = getContextClient();
|
||||||
|
|
||||||
|
let pendingChange = "none";
|
||||||
|
|
||||||
|
const removeTagMutation = ({ tagIds }) => {
|
||||||
|
return mutationStore({
|
||||||
|
client: client,
|
||||||
|
query: gql`
|
||||||
|
mutation ($tagIds: [ID!]!) {
|
||||||
|
removeTagFromList(tagIds: $tagIds)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: { tagIds },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function removeTag(tag, tagType) {
|
||||||
|
if (confirm("Are you sure you want to completely remove this tag?\n\n" + tagType + ':' + tag.name)) {
|
||||||
|
pendingChange = tagType;
|
||||||
|
removeTagMutation({tagIds: [tag.id] }).subscribe(
|
||||||
|
(res) => {
|
||||||
|
if (res.fetching === false && !res.error) {
|
||||||
|
tagmap[tagType] = tagmap[tagType].filter((t) => !res.data.removeTagFromList.includes(t.id));
|
||||||
|
if (tagmap[tagType].length === 0) {
|
||||||
|
delete tagmap[tagType]
|
||||||
|
}
|
||||||
|
pendingChange = "none";
|
||||||
|
} else if (res.fetching === false && res.error) {
|
||||||
|
throw res.error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-10">
|
||||||
|
{#each Object.entries(tagmap) as [tagType, tagList]}
|
||||||
|
<div class="my-3 p-2 bg-secondary rounded text-white"> <!-- text-capitalize -->
|
||||||
|
Tag Type: <b>{tagType}</b>
|
||||||
|
{#if pendingChange === tagType}
|
||||||
|
<Spinner size="sm" secondary />
|
||||||
|
{/if}
|
||||||
|
<span style="float: right; padding-bottom: 0.4rem; padding-top: 0.4rem;" class="badge bg-light text-secondary">
|
||||||
|
{tagList.length} Tag{(tagList.length != 1)?'s':''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-inline-flex flex-wrap">
|
||||||
|
{#each tagList as tag (tag.id)}
|
||||||
|
<InputGroup class="w-auto flex-nowrap" style="margin-right: 0.5rem; margin-bottom: 0.5rem;">
|
||||||
|
<Button outline color="secondary" href="/monitoring/jobs/?tag={tag.id}" target="_blank">
|
||||||
|
<Badge color="light" style="font-size:medium;" border>{tag.name}</Badge> :
|
||||||
|
<Badge color="primary" pill>{tag.count} Job{(tag.count != 1)?'s':''}</Badge>
|
||||||
|
{#if tag.scope == "global"}
|
||||||
|
<Badge style="background-color:#c85fc8 !important;" pill>Global</Badge>
|
||||||
|
{:else if tag.scope == "admin"}
|
||||||
|
<Badge style="background-color:#19e5e6 !important;" pill>Admin</Badge>
|
||||||
|
{:else}
|
||||||
|
<Badge color="warning" pill>Private</Badge>
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
{#if (isAdmin && (tag.scope == "admin" || tag.scope == "global")) || tag.scope == username }
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="danger"
|
||||||
|
on:click={() => removeTag(tag, tagType)}
|
||||||
|
>
|
||||||
|
<Icon name="x" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</InputGroup>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
16
web/frontend/src/tags.entrypoint.js
Normal file
16
web/frontend/src/tags.entrypoint.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import {} from './header.entrypoint.js'
|
||||||
|
import Tags from './Tags.root.svelte'
|
||||||
|
|
||||||
|
new Tags({
|
||||||
|
target: document.getElementById('svelte-app'),
|
||||||
|
props: {
|
||||||
|
username: username,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
tagmap: tagmap,
|
||||||
|
},
|
||||||
|
context: new Map([
|
||||||
|
['cc-config', clusterCockpitConfig]
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
|
@ -1,37 +1,15 @@
|
|||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="container">
|
<div id="svelte-app"></div>
|
||||||
<div class="row justify-content-center">
|
{{end}}
|
||||||
<div class="col-10">
|
{{define "stylesheets"}}
|
||||||
{{ range $tagType, $tagList := .Infos.tagmap }}
|
<link rel='stylesheet' href='/build/taglist.css'>
|
||||||
<div class="my-3 p-2 bg-secondary rounded text-white"> <!-- text-capitalize -->
|
{{end}}
|
||||||
Tag Type: <b>{{ $tagType }}</b>
|
{{define "javascript"}}
|
||||||
<span style="float: right; padding-bottom: 0.4rem; padding-top: 0.4rem;" class="badge bg-light text-secondary">
|
<script>
|
||||||
{{len $tagList}} Tag{{if ne (len $tagList) 1}}s{{end}}
|
const username = {{ .User.Username }};
|
||||||
</span>
|
const isAdmin = {{ .User.HasRole .Roles.admin }};
|
||||||
</div>
|
const tagmap = {{ .Infos.tagmap }};
|
||||||
{{ range $tagList }}
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
{{if eq .scope "global"}}
|
</script>
|
||||||
<a class="btn btn-outline-secondary" href="/monitoring/jobs/?tag={{ .id }}" role="button">
|
<script src='/build/taglist.js'></script>
|
||||||
{{ .name }}
|
|
||||||
<span class="badge bg-primary mr-1">{{ .count }} Job{{if ne .count 1}}s{{end}}</span>
|
|
||||||
<span style="background-color:#c85fc8;" class="badge text-dark">Global</span>
|
|
||||||
</a>
|
|
||||||
{{else if eq .scope "admin"}}
|
|
||||||
<a class="btn btn-outline-secondary" href="/monitoring/jobs/?tag={{ .id }}" role="button">
|
|
||||||
{{ .name }}
|
|
||||||
<span class="badge bg-primary mr-1">{{ .count }} Job{{if ne .count 1}}s{{end}}</span>
|
|
||||||
<span style="background-color:#19e5e6;" class="badge text-dark">Admin</span>
|
|
||||||
</a>
|
|
||||||
{{else}}
|
|
||||||
<a class="btn btn-outline-secondary" href="/monitoring/jobs/?tag={{ .id }}" role="button">
|
|
||||||
{{ .name }}
|
|
||||||
<span class="badge bg-primary mr-1">{{ .count }} Job{{if ne .count 1}}s{{end}}</span>
|
|
||||||
<span class="badge bg-warning text-dark">Private</span>
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user