mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 05:19:05 +01:00
feat: Add tag scopes to front and backend, initial commit
This commit is contained in:
parent
c80d3a6958
commit
017f9b2140
@ -113,6 +113,7 @@ type Tag {
|
|||||||
id: ID!
|
id: ID!
|
||||||
type: String!
|
type: String!
|
||||||
name: String!
|
name: String!
|
||||||
|
scope: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Resource {
|
type Resource {
|
||||||
@ -235,7 +236,7 @@ type Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createTag(type: String!, name: String!): Tag!
|
createTag(type: String!, name: String!, scope: String!): Tag!
|
||||||
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!]!
|
||||||
|
@ -176,8 +176,9 @@ type ErrorResponse struct {
|
|||||||
// ApiTag model
|
// ApiTag model
|
||||||
type ApiTag struct {
|
type ApiTag struct {
|
||||||
// Tag Type
|
// Tag Type
|
||||||
Type string `json:"type" example:"Debug"`
|
Type string `json:"type" example:"Debug"`
|
||||||
Name string `json:"name" example:"Testjob"` // Tag Name
|
Name string `json:"name" example:"Testjob"` // Tag Name
|
||||||
|
Scope string `json:"scope" example:"global"` // Tag Scope for Frontend Display
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiMeta model
|
// ApiMeta model
|
||||||
@ -419,7 +420,7 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
|
|||||||
StartTime: job.StartTime.Unix(),
|
StartTime: job.StartTime.Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Tags, err = api.JobRepository.GetTags(&job.ID)
|
res.Tags, err = api.JobRepository.GetTags(r.Context(), &job.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(err, http.StatusInternalServerError, rw)
|
handleError(err, http.StatusInternalServerError, rw)
|
||||||
return
|
return
|
||||||
@ -492,7 +493,7 @@ func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Tags, err = api.JobRepository.GetTags(&job.ID)
|
job.Tags, err = api.JobRepository.GetTags(r.Context(), &job.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(err, http.StatusInternalServerError, rw)
|
handleError(err, http.StatusInternalServerError, rw)
|
||||||
return
|
return
|
||||||
@ -578,7 +579,7 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Tags, err = api.JobRepository.GetTags(&job.ID)
|
job.Tags, err = api.JobRepository.GetTags(r.Context(), &job.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(err, http.StatusInternalServerError, rw)
|
handleError(err, http.StatusInternalServerError, rw)
|
||||||
return
|
return
|
||||||
@ -711,7 +712,7 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Tags, err = api.JobRepository.GetTags(&job.ID)
|
job.Tags, err = api.JobRepository.GetTags(r.Context(), &job.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@ -724,16 +725,17 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range req {
|
for _, tag := range req {
|
||||||
tagId, err := api.JobRepository.AddTagOrCreate(job.ID, tag.Type, tag.Name)
|
tagId, err := api.JobRepository.AddTagOrCreate(r.Context(), job.ID, tag.Type, tag.Name, tag.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job.Tags = append(job.Tags, &schema.Tag{
|
job.Tags = append(job.Tags, &schema.Tag{
|
||||||
ID: tagId,
|
ID: tagId,
|
||||||
Type: tag.Type,
|
Type: tag.Type,
|
||||||
Name: tag.Name,
|
Name: tag.Name,
|
||||||
|
Scope: tag.Scope,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,7 +803,7 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
unlockOnce.Do(api.RepositoryMutex.Unlock)
|
unlockOnce.Do(api.RepositoryMutex.Unlock)
|
||||||
|
|
||||||
for _, tag := range req.Tags {
|
for _, tag := range req.Tags {
|
||||||
if _, err := api.JobRepository.AddTagOrCreate(id, tag.Type, tag.Name); err != nil {
|
if _, err := api.JobRepository.AddTagOrCreate(r.Context(), id, tag.Type, tag.Name, tag.Scope); err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
handleError(fmt.Errorf("adding tag to new job %d failed: %w", id, err), http.StatusInternalServerError, rw)
|
handleError(fmt.Errorf("adding tag to new job %d failed: %w", id, err), http.StatusInternalServerError, rw)
|
||||||
return
|
return
|
||||||
|
@ -229,7 +229,7 @@ type ComplexityRoot struct {
|
|||||||
|
|
||||||
Mutation struct {
|
Mutation 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) 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
|
||||||
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
|
||||||
@ -303,9 +303,10 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Tag struct {
|
Tag struct {
|
||||||
ID func(childComplexity int) int
|
ID func(childComplexity int) int
|
||||||
Name func(childComplexity int) int
|
Name func(childComplexity int) int
|
||||||
Type func(childComplexity int) int
|
Scope func(childComplexity int) int
|
||||||
|
Type func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeRangeOutput struct {
|
TimeRangeOutput struct {
|
||||||
@ -355,7 +356,7 @@ type MetricValueResolver interface {
|
|||||||
Name(ctx context.Context, obj *schema.MetricValue) (*string, error)
|
Name(ctx context.Context, obj *schema.MetricValue) (*string, error)
|
||||||
}
|
}
|
||||||
type MutationResolver interface {
|
type MutationResolver interface {
|
||||||
CreateTag(ctx context.Context, typeArg string, name string) (*schema.Tag, error)
|
CreateTag(ctx context.Context, typeArg string, name string, scope string) (*schema.Tag, error)
|
||||||
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)
|
||||||
@ -1183,7 +1184,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Mutation.CreateTag(childComplexity, args["type"].(string), args["name"].(string)), true
|
return e.complexity.Mutation.CreateTag(childComplexity, args["type"].(string), args["name"].(string), args["scope"].(string)), true
|
||||||
|
|
||||||
case "Mutation.deleteTag":
|
case "Mutation.deleteTag":
|
||||||
if e.complexity.Mutation.DeleteTag == nil {
|
if e.complexity.Mutation.DeleteTag == nil {
|
||||||
@ -1602,6 +1603,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Tag.Name(childComplexity), true
|
return e.complexity.Tag.Name(childComplexity), true
|
||||||
|
|
||||||
|
case "Tag.scope":
|
||||||
|
if e.complexity.Tag.Scope == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Tag.Scope(childComplexity), true
|
||||||
|
|
||||||
case "Tag.type":
|
case "Tag.type":
|
||||||
if e.complexity.Tag.Type == nil {
|
if e.complexity.Tag.Type == nil {
|
||||||
break
|
break
|
||||||
@ -1949,6 +1957,7 @@ type Tag {
|
|||||||
id: ID!
|
id: ID!
|
||||||
type: String!
|
type: String!
|
||||||
name: String!
|
name: String!
|
||||||
|
scope: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Resource {
|
type Resource {
|
||||||
@ -2071,7 +2080,7 @@ type Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createTag(type: String!, name: String!): Tag!
|
createTag(type: String!, name: String!, scope: String!): Tag!
|
||||||
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!]!
|
||||||
@ -2244,6 +2253,15 @@ func (ec *executionContext) field_Mutation_createTag_args(ctx context.Context, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
args["name"] = arg1
|
args["name"] = arg1
|
||||||
|
var arg2 string
|
||||||
|
if tmp, ok := rawArgs["scope"]; ok {
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
|
||||||
|
arg2, err = ec.unmarshalNString2string(ctx, tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args["scope"] = arg2
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4622,6 +4640,8 @@ func (ec *executionContext) fieldContext_Job_tags(_ context.Context, field graph
|
|||||||
return ec.fieldContext_Tag_type(ctx, field)
|
return ec.fieldContext_Tag_type(ctx, field)
|
||||||
case "name":
|
case "name":
|
||||||
return ec.fieldContext_Tag_name(ctx, field)
|
return ec.fieldContext_Tag_name(ctx, field)
|
||||||
|
case "scope":
|
||||||
|
return ec.fieldContext_Tag_scope(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
||||||
},
|
},
|
||||||
@ -7680,7 +7700,7 @@ func (ec *executionContext) _Mutation_createTag(ctx context.Context, field graph
|
|||||||
}()
|
}()
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
ctx = rctx // use context from middleware stack in children
|
||||||
return ec.resolvers.Mutation().CreateTag(rctx, fc.Args["type"].(string), fc.Args["name"].(string))
|
return ec.resolvers.Mutation().CreateTag(rctx, fc.Args["type"].(string), fc.Args["name"].(string), fc.Args["scope"].(string))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
@ -7711,6 +7731,8 @@ func (ec *executionContext) fieldContext_Mutation_createTag(ctx context.Context,
|
|||||||
return ec.fieldContext_Tag_type(ctx, field)
|
return ec.fieldContext_Tag_type(ctx, field)
|
||||||
case "name":
|
case "name":
|
||||||
return ec.fieldContext_Tag_name(ctx, field)
|
return ec.fieldContext_Tag_name(ctx, field)
|
||||||
|
case "scope":
|
||||||
|
return ec.fieldContext_Tag_scope(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
||||||
},
|
},
|
||||||
@ -7829,6 +7851,8 @@ func (ec *executionContext) fieldContext_Mutation_addTagsToJob(ctx context.Conte
|
|||||||
return ec.fieldContext_Tag_type(ctx, field)
|
return ec.fieldContext_Tag_type(ctx, field)
|
||||||
case "name":
|
case "name":
|
||||||
return ec.fieldContext_Tag_name(ctx, field)
|
return ec.fieldContext_Tag_name(ctx, field)
|
||||||
|
case "scope":
|
||||||
|
return ec.fieldContext_Tag_scope(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
||||||
},
|
},
|
||||||
@ -7892,6 +7916,8 @@ func (ec *executionContext) fieldContext_Mutation_removeTagsFromJob(ctx context.
|
|||||||
return ec.fieldContext_Tag_type(ctx, field)
|
return ec.fieldContext_Tag_type(ctx, field)
|
||||||
case "name":
|
case "name":
|
||||||
return ec.fieldContext_Tag_name(ctx, field)
|
return ec.fieldContext_Tag_name(ctx, field)
|
||||||
|
case "scope":
|
||||||
|
return ec.fieldContext_Tag_scope(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
||||||
},
|
},
|
||||||
@ -8199,6 +8225,8 @@ func (ec *executionContext) fieldContext_Query_tags(_ context.Context, field gra
|
|||||||
return ec.fieldContext_Tag_type(ctx, field)
|
return ec.fieldContext_Tag_type(ctx, field)
|
||||||
case "name":
|
case "name":
|
||||||
return ec.fieldContext_Tag_name(ctx, field)
|
return ec.fieldContext_Tag_name(ctx, field)
|
||||||
|
case "scope":
|
||||||
|
return ec.fieldContext_Tag_scope(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Tag", field.Name)
|
||||||
},
|
},
|
||||||
@ -10547,6 +10575,50 @@ func (ec *executionContext) fieldContext_Tag_name(_ context.Context, field graph
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Tag_scope(ctx context.Context, field graphql.CollectedField, obj *schema.Tag) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_Tag_scope(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) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Scope, nil
|
||||||
|
})
|
||||||
|
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.(string)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_Tag_scope(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "Tag",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return nil, errors.New("field of type String does not have child fields")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _TimeRangeOutput_from(ctx context.Context, field graphql.CollectedField, obj *model.TimeRangeOutput) (ret graphql.Marshaler) {
|
func (ec *executionContext) _TimeRangeOutput_from(ctx context.Context, field graphql.CollectedField, obj *model.TimeRangeOutput) (ret graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext_TimeRangeOutput_from(ctx, field)
|
fc, err := ec.fieldContext_TimeRangeOutput_from(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -15666,6 +15738,11 @@ func (ec *executionContext) _Tag(ctx context.Context, sel ast.SelectionSet, obj
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
|
case "scope":
|
||||||
|
out.Values[i] = ec._Tag_scope(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
out.Invalids++
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func (r *clusterResolver) Partitions(ctx context.Context, obj *schema.Cluster) (
|
|||||||
|
|
||||||
// Tags is the resolver for the tags field.
|
// Tags is the resolver for the tags field.
|
||||||
func (r *jobResolver) Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag, error) {
|
func (r *jobResolver) Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag, error) {
|
||||||
return r.Repo.GetTags(&obj.ID)
|
return r.Repo.GetTags(ctx, &obj.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConcurrentJobs is the resolver for the concurrentJobs field.
|
// ConcurrentJobs is the resolver for the concurrentJobs field.
|
||||||
@ -86,14 +86,14 @@ 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) (*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)
|
id, err := r.Repo.CreateTag(typeArg, name, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while creating tag")
|
log.Warn("Error while creating tag")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &schema.Tag{ID: id, Type: typeArg, Name: name}, nil
|
return &schema.Tag{ID: id, Type: typeArg, Name: name, Scope: scope}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTag is the resolver for the deleteTag field.
|
// DeleteTag is the resolver for the deleteTag field.
|
||||||
@ -103,6 +103,7 @@ func (r *mutationResolver) DeleteTag(ctx context.Context, id string) (string, er
|
|||||||
|
|
||||||
// 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
|
||||||
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")
|
||||||
@ -117,7 +118,7 @@ func (r *mutationResolver) AddTagsToJob(ctx context.Context, job string, tagIds
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tags, err = r.Repo.AddTag(jid, tid); err != nil {
|
if tags, err = r.Repo.AddTag(ctx, jid, tid); err != nil {
|
||||||
log.Warn("Error while adding tag")
|
log.Warn("Error while adding tag")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -128,6 +129,7 @@ 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
|
||||||
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")
|
||||||
@ -142,7 +144,7 @@ func (r *mutationResolver) RemoveTagsFromJob(ctx context.Context, job string, ta
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tags, err = r.Repo.RemoveTag(jid, tid); err != nil {
|
if tags, err = r.Repo.RemoveTag(ctx, jid, tid); err != nil {
|
||||||
log.Warn("Error while removing tag")
|
log.Warn("Error while removing tag")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -168,7 +170,7 @@ func (r *queryResolver) Clusters(ctx context.Context) ([]*schema.Cluster, error)
|
|||||||
|
|
||||||
// Tags is the resolver for the tags field.
|
// Tags is the resolver for the tags field.
|
||||||
func (r *queryResolver) Tags(ctx context.Context) ([]*schema.Tag, error) {
|
func (r *queryResolver) Tags(ctx context.Context) ([]*schema.Tag, error) {
|
||||||
return r.Repo.GetTags(nil)
|
return r.Repo.GetTags(ctx, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GlobalMetrics is the resolver for the globalMetrics field.
|
// GlobalMetrics is the resolver for the globalMetrics field.
|
||||||
|
@ -112,8 +112,8 @@ func HandleImportFlag(flag string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range job.Tags {
|
for _, tag := range job.Tags {
|
||||||
if _, err := r.AddTagOrCreate(id, tag.Type, tag.Name); err != nil {
|
if err := r.ImportTag(id, tag.Type, tag.Name, tag.Scope); err != nil {
|
||||||
log.Error("Error while adding or creating tag")
|
log.Error("Error while adding or creating tag on import")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/pkg/archive"
|
"github.com/ClusterCockpit/cc-backend/pkg/archive"
|
||||||
@ -14,7 +16,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Add the tag with id `tagId` to the job with the database id `jobId`.
|
// Add the tag with id `tagId` to the job with the database id `jobId`.
|
||||||
func (r *JobRepository) AddTag(job int64, tag int64) ([]*schema.Tag, error) {
|
func (r *JobRepository) AddTag(ctx context.Context, job int64, tag int64) ([]*schema.Tag, error) {
|
||||||
|
|
||||||
|
j, err := r.FindById(ctx, job)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while finding job by id")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
q := sq.Insert("jobtag").Columns("job_id", "tag_id").Values(job, tag)
|
q := sq.Insert("jobtag").Columns("job_id", "tag_id").Values(job, tag)
|
||||||
|
|
||||||
if _, err := q.RunWith(r.stmtCache).Exec(); err != nil {
|
if _, err := q.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
@ -23,49 +32,56 @@ func (r *JobRepository) AddTag(job int64, tag int64) ([]*schema.Tag, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := r.FindByIdDirect(job)
|
tags, err := r.GetTags(ctx, &job)
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error while finding job by id")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := r.GetTags(&job)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while getting tags for job")
|
log.Warn("Error while getting tags for job")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags, archive.UpdateTags(j, tags)
|
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 a job
|
// Removes a tag from a job
|
||||||
func (r *JobRepository) RemoveTag(job, tag int64) ([]*schema.Tag, error) {
|
func (r *JobRepository) RemoveTag(ctx context.Context, job, tag int64) ([]*schema.Tag, error) {
|
||||||
|
|
||||||
|
j, err := r.FindById(ctx, job)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error while finding job by id")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
q := sq.Delete("jobtag").Where("jobtag.job_id = ?", job).Where("jobtag.tag_id = ?", tag)
|
q := sq.Delete("jobtag").Where("jobtag.job_id = ?", job).Where("jobtag.tag_id = ?", tag)
|
||||||
|
|
||||||
if _, err := q.RunWith(r.stmtCache).Exec(); err != nil {
|
if _, err := q.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
s, _, _ := q.ToSql()
|
s, _, _ := q.ToSql()
|
||||||
log.Errorf("Error adding tag with %s: %v", s, err)
|
log.Errorf("Error removing tag with %s: %v", s, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
j, err := r.FindByIdDirect(job)
|
tags, err := r.GetTags(ctx, &job)
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error while finding job by id")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := r.GetTags(&job)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error while getting tags for job")
|
log.Warn("Error while getting tags for job")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags, archive.UpdateTags(j, tags)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) (tagId int64, err error) {
|
func (r *JobRepository) CreateTag(tagType string, tagName string, tagScope string) (tagId int64, err error) {
|
||||||
q := sq.Insert("tag").Columns("tag_type", "tag_name").Values(tagType, tagName)
|
q := sq.Insert("tag").Columns("tag_type", "tag_name", "tag_scope").Values(tagType, tagName, tagScope)
|
||||||
|
|
||||||
res, err := q.RunWith(r.stmtCache).Exec()
|
res, err := q.RunWith(r.stmtCache).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -77,9 +93,10 @@ func (r *JobRepository) CreateTag(tagType string, tagName string) (tagId int64,
|
|||||||
return res.LastInsertId()
|
return res.LastInsertId()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *JobRepository) CountTags(user *schema.User) (tags []schema.Tag, counts map[string]int, err error) {
|
func (r *JobRepository) CountTags(ctx context.Context) (tags []schema.Tag, counts map[string]int, err error) {
|
||||||
|
// Fetch all Tags in DB for Display in Frontend Tag-View
|
||||||
tags = make([]schema.Tag, 0, 100)
|
tags = make([]schema.Tag, 0, 100)
|
||||||
xrows, err := r.DB.Queryx("SELECT id, tag_type, tag_name FROM tag")
|
xrows, err := r.DB.Queryx("SELECT id, tag_type, tag_name, tag_scope FROM tag")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -89,14 +106,36 @@ func (r *JobRepository) CountTags(user *schema.User) (tags []schema.Tag, counts
|
|||||||
if err = xrows.StructScan(&t); err != nil {
|
if err = xrows.StructScan(&t); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
tags = append(tags, t)
|
|
||||||
|
// Handle Scope Filtering: Tag Scope is Global, Private (== Username) or User is auth'd to view Admin Tags
|
||||||
|
readable, err := r.checkScopeAuth(ctx, "read", t.Scope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if readable {
|
||||||
|
tags = append(tags, t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user := GetUserFromContext(ctx)
|
||||||
|
|
||||||
|
// Query and Count Jobs with attached Tags
|
||||||
q := sq.Select("t.tag_name, count(jt.tag_id)").
|
q := sq.Select("t.tag_name, count(jt.tag_id)").
|
||||||
From("tag t").
|
From("tag t").
|
||||||
LeftJoin("jobtag jt ON t.id = jt.tag_id").
|
LeftJoin("jobtag jt ON t.id = jt.tag_id").
|
||||||
GroupBy("t.tag_name")
|
GroupBy("t.tag_name")
|
||||||
|
|
||||||
|
// Handle Scope Filtering
|
||||||
|
scopeList := "\"global\""
|
||||||
|
if user != nil {
|
||||||
|
scopeList += ",\"" + user.Username + "\""
|
||||||
|
}
|
||||||
|
if user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) {
|
||||||
|
scopeList += ",\"admin\""
|
||||||
|
}
|
||||||
|
q = q.Where("t.tag_scope IN (" + scopeList + ")")
|
||||||
|
|
||||||
|
// Handle Job Ownership
|
||||||
if user != nil && user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) { // ADMIN || SUPPORT: Count all jobs
|
if user != nil && user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) { // ADMIN || SUPPORT: Count all jobs
|
||||||
log.Debug("CountTags: User Admin or Support -> Count all Jobs for Tags")
|
log.Debug("CountTags: User Admin or Support -> Count all Jobs for Tags")
|
||||||
// Unchanged: Needs to be own case still, due to UserRole/NoRole compatibility handling in else case
|
// Unchanged: Needs to be own case still, due to UserRole/NoRole compatibility handling in else case
|
||||||
@ -123,21 +162,30 @@ func (r *JobRepository) CountTags(user *schema.User) (tags []schema.Tag, counts
|
|||||||
}
|
}
|
||||||
err = rows.Err()
|
err = rows.Err()
|
||||||
|
|
||||||
return
|
return tags, counts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTagOrCreate adds the tag with the specified type and name to the job with the database id `jobId`.
|
// AddTagOrCreate adds the tag with the specified type and name to the job with the database id `jobId`.
|
||||||
// If such a tag does not yet exist, it is created.
|
// If such a tag does not yet exist, it is created.
|
||||||
func (r *JobRepository) AddTagOrCreate(jobId int64, tagType string, tagName string) (tagId int64, err error) {
|
func (r *JobRepository) AddTagOrCreate(ctx context.Context, jobId int64, tagType string, tagName string, tagScope string) (tagId int64, err error) {
|
||||||
tagId, exists := r.TagId(tagType, tagName)
|
|
||||||
|
writable, err := r.checkScopeAuth(ctx, "write", tagScope)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if !writable {
|
||||||
|
return 0, fmt.Errorf("cannot write tag scope with current authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
tagId, exists := r.TagId(tagType, tagName, tagScope)
|
||||||
if !exists {
|
if !exists {
|
||||||
tagId, err = r.CreateTag(tagType, tagName)
|
tagId, err = r.CreateTag(tagType, tagName, tagScope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := r.AddTag(jobId, tagId); err != nil {
|
if _, err := r.AddTag(ctx, jobId, tagId); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,19 +193,19 @@ func (r *JobRepository) AddTagOrCreate(jobId int64, tagType string, tagName stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TagId returns the database id of the tag with the specified type and name.
|
// TagId returns the database id of the tag with the specified type and name.
|
||||||
func (r *JobRepository) TagId(tagType string, tagName string) (tagId int64, exists bool) {
|
func (r *JobRepository) TagId(tagType string, tagName string, tagScope string) (tagId int64, exists bool) {
|
||||||
exists = true
|
exists = true
|
||||||
if err := sq.Select("id").From("tag").
|
if err := sq.Select("id").From("tag").
|
||||||
Where("tag.tag_type = ?", tagType).Where("tag.tag_name = ?", tagName).
|
Where("tag.tag_type = ?", tagType).Where("tag.tag_name = ?", tagName).Where("tag.tag_scope = ?", tagScope).
|
||||||
RunWith(r.stmtCache).QueryRow().Scan(&tagId); err != nil {
|
RunWith(r.stmtCache).QueryRow().Scan(&tagId); err != nil {
|
||||||
exists = false
|
exists = false
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTags returns a list of all 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(job *int64) ([]*schema.Tag, error) {
|
func (r *JobRepository) GetTags(ctx context.Context, job *int64) ([]*schema.Tag, error) {
|
||||||
q := sq.Select("id", "tag_type", "tag_name").From("tag")
|
q := sq.Select("id", "tag_type", "tag_name", "tag_scope").From("tag")
|
||||||
if job != nil {
|
if job != nil {
|
||||||
q = q.Join("jobtag ON jobtag.tag_id = tag.id").Where("jobtag.job_id = ?", *job)
|
q = q.Join("jobtag ON jobtag.tag_id = tag.id").Where("jobtag.job_id = ?", *job)
|
||||||
}
|
}
|
||||||
@ -172,7 +220,41 @@ func (r *JobRepository) GetTags(job *int64) ([]*schema.Tag, error) {
|
|||||||
tags := make([]*schema.Tag, 0)
|
tags := make([]*schema.Tag, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
tag := &schema.Tag{}
|
tag := &schema.Tag{}
|
||||||
if err := rows.Scan(&tag.ID, &tag.Type, &tag.Name); err != nil {
|
if err := rows.Scan(&tag.ID, &tag.Type, &tag.Name, &tag.Scope); err != nil {
|
||||||
|
log.Warn("Error while scanning rows")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Handle Scope Filtering: Tag Scope is Global, Private (== Username) or User is auth'd to view Admin Tags
|
||||||
|
readable, err := r.checkScopeAuth(ctx, "read", tag.Scope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if readable {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArchiveTags returns a list of all tags *regardless of scope* if job is nil or of the tags that the job with that database ID has.
|
||||||
|
func (r *JobRepository) getArchiveTags(job *int64) ([]*schema.Tag, error) {
|
||||||
|
q := sq.Select("id", "tag_type", "tag_name", "tag_scope").From("tag")
|
||||||
|
if job != nil {
|
||||||
|
q = q.Join("jobtag ON jobtag.tag_id = tag.id").Where("jobtag.job_id = ?", *job)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := q.RunWith(r.stmtCache).Query()
|
||||||
|
if err != nil {
|
||||||
|
s, _, _ := q.ToSql()
|
||||||
|
log.Errorf("Error get tags with %s: %v", s, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := make([]*schema.Tag, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
tag := &schema.Tag{}
|
||||||
|
if err := rows.Scan(&tag.ID, &tag.Type, &tag.Name, &tag.Scope); err != nil {
|
||||||
log.Warn("Error while scanning rows")
|
log.Warn("Error while scanning rows")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -181,3 +263,60 @@ func (r *JobRepository) GetTags(job *int64) ([]*schema.Tag, error) {
|
|||||||
|
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *JobRepository) ImportTag(jobId int64, tagType string, tagName string, tagScope string) (err error) {
|
||||||
|
// Import has no scope ctx, only import from metafile to DB (No recursive archive update required), only returns err
|
||||||
|
|
||||||
|
tagId, exists := r.TagId(tagType, tagName, tagScope)
|
||||||
|
if !exists {
|
||||||
|
tagId, err = r.CreateTag(tagType, tagName, tagScope)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
q := sq.Insert("jobtag").Columns("job_id", "tag_id").Values(jobId, tagId)
|
||||||
|
|
||||||
|
if _, err := q.RunWith(r.stmtCache).Exec(); err != nil {
|
||||||
|
s, _, _ := q.ToSql()
|
||||||
|
log.Errorf("Error adding tag on import with %s: %v", s, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *JobRepository) checkScopeAuth(ctx context.Context, operation string, scope string) (pass bool, err error) {
|
||||||
|
user := GetUserFromContext(ctx)
|
||||||
|
if user != nil {
|
||||||
|
switch {
|
||||||
|
case operation == "write" && scope == "admin":
|
||||||
|
if user.HasRole(schema.RoleAdmin) || (len(user.Roles) == 1 && user.HasRole(schema.RoleApi)) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
case operation == "write" && scope == "global":
|
||||||
|
if user.HasRole(schema.RoleAdmin) || (len(user.Roles) == 1 && user.HasRole(schema.RoleApi)) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
case operation == "write" && scope == user.Username:
|
||||||
|
return true, nil
|
||||||
|
case operation == "read" && scope == "admin":
|
||||||
|
return user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}), nil
|
||||||
|
case operation == "read" && scope == "global":
|
||||||
|
return true, nil
|
||||||
|
case operation == "read" && scope == user.Username:
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
if operation == "read" || operation == "write" {
|
||||||
|
// No acceptable scope: deny tag
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
return false, fmt.Errorf("error while checking tag operation auth: unknown operation (%s)", operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false, fmt.Errorf("error while checking tag operation auth: no user in context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -124,9 +124,8 @@ func setupAnalysisRoute(i InfoType, r *http.Request) InfoType {
|
|||||||
|
|
||||||
func setupTaglistRoute(i InfoType, r *http.Request) InfoType {
|
func setupTaglistRoute(i InfoType, r *http.Request) InfoType {
|
||||||
jobRepo := repository.GetJobRepository()
|
jobRepo := repository.GetJobRepository()
|
||||||
user := repository.GetUserFromContext(r.Context())
|
|
||||||
|
|
||||||
tags, counts, err := jobRepo.CountTags(user)
|
tags, counts, err := jobRepo.CountTags(r.Context())
|
||||||
tagMap := make(map[string][]map[string]interface{})
|
tagMap := make(map[string][]map[string]interface{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("GetTags failed: %s", err.Error())
|
log.Warnf("GetTags failed: %s", err.Error())
|
||||||
@ -138,6 +137,7 @@ func setupTaglistRoute(i InfoType, r *http.Request) InfoType {
|
|||||||
tagItem := map[string]interface{}{
|
tagItem := map[string]interface{}{
|
||||||
"id": tag.ID,
|
"id": tag.ID,
|
||||||
"name": tag.Name,
|
"name": tag.Name,
|
||||||
|
"scope": tag.Scope,
|
||||||
"count": counts[tag.Name],
|
"count": counts[tag.Name],
|
||||||
}
|
}
|
||||||
tagMap[tag.Type] = append(tagMap[tag.Type], tagItem)
|
tagMap[tag.Type] = append(tagMap[tag.Type], tagItem)
|
||||||
|
@ -171,8 +171,9 @@ func UpdateTags(job *schema.Job, tags []*schema.Tag) error {
|
|||||||
jobMeta.Tags = make([]*schema.Tag, 0)
|
jobMeta.Tags = make([]*schema.Tag, 0)
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
jobMeta.Tags = append(jobMeta.Tags, &schema.Tag{
|
jobMeta.Tags = append(jobMeta.Tags, &schema.Tag{
|
||||||
Name: tag.Name,
|
Name: tag.Name,
|
||||||
Type: tag.Type,
|
Type: tag.Type,
|
||||||
|
Scope: tag.Scope,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,9 +117,10 @@ type JobStatistics struct {
|
|||||||
// Tag model
|
// Tag model
|
||||||
// @Description Defines a tag using name and type.
|
// @Description Defines a tag using name and type.
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Type string `json:"type" db:"tag_type" example:"Debug"`
|
Type string `json:"type" db:"tag_type" example:"Debug"`
|
||||||
Name string `json:"name" db:"tag_name" example:"Testjob"`
|
Name string `json:"name" db:"tag_name" example:"Testjob"`
|
||||||
ID int64 `json:"id" db:"id"`
|
Scope string `json:"scope" db:"tag_scope" example:"global"`
|
||||||
|
ID int64 `json:"id" db:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resource model
|
// Resource model
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
@component Main single job display component; displays plots for every metric as well as various information
|
@component Main single job display component; displays plots for every metric as well as various information
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
|
- `dbid Number`: The jobs DB ID
|
||||||
- `username String`: Empty string if auth. is disabled, otherwise the username as string
|
- `username String`: Empty string if auth. is disabled, otherwise the username as string
|
||||||
- `authlevel Number`: The current users authentication level
|
- `authlevel Number`: The current users authentication level
|
||||||
- `clusters [String]`: List of cluster names
|
|
||||||
- `roles [Number]`: Enum containing available roles
|
- `roles [Number]`: Enum containing available roles
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -45,6 +45,7 @@
|
|||||||
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
||||||
|
|
||||||
export let dbid;
|
export let dbid;
|
||||||
|
export let username;
|
||||||
export let authlevel;
|
export let authlevel;
|
||||||
export let roles;
|
export let roles;
|
||||||
|
|
||||||
@ -58,8 +59,7 @@
|
|||||||
selectedScopes = [];
|
selectedScopes = [];
|
||||||
|
|
||||||
let plots = {},
|
let plots = {},
|
||||||
jobTags,
|
jobTags
|
||||||
statsTable
|
|
||||||
|
|
||||||
let missingMetrics = [],
|
let missingMetrics = [],
|
||||||
missingHosts = [],
|
missingHosts = [],
|
||||||
@ -322,7 +322,7 @@
|
|||||||
<Row class="mb-3">
|
<Row class="mb-3">
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
{#if $initq.data}
|
{#if $initq.data}
|
||||||
<TagManagement job={$initq.data.job} bind:jobTags />
|
<TagManagement job={$initq.data.job} {username} {authlevel} {roles} bind:jobTags />
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
@ -418,7 +418,6 @@
|
|||||||
{#if $jobMetrics?.data?.jobMetrics}
|
{#if $jobMetrics?.data?.jobMetrics}
|
||||||
{#key $jobMetrics.data.jobMetrics}
|
{#key $jobMetrics.data.jobMetrics}
|
||||||
<StatsTable
|
<StatsTable
|
||||||
bind:this={statsTable}
|
|
||||||
job={$initq.data.job}
|
job={$initq.data.job}
|
||||||
jobMetrics={$jobMetrics.data.jobMetrics}
|
jobMetrics={$jobMetrics.data.jobMetrics}
|
||||||
/>
|
/>
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
import {
|
import {
|
||||||
init,
|
init,
|
||||||
checkMetricDisabled,
|
checkMetricDisabled,
|
||||||
} from "./utils.js";
|
} from "./generic/utils.js";
|
||||||
import PlotTable from "./generic/PlotTable.svelte";
|
import PlotTable from "./generic/PlotTable.svelte";
|
||||||
import MetricPlot from "./generic/plots/MetricPlot.svelte";
|
import MetricPlot from "./generic/plots/MetricPlot.svelte";
|
||||||
import TimeSelection from "./generic/select/TimeSelection.svelte";
|
import TimeSelection from "./generic/select/TimeSelection.svelte";
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
id
|
id
|
||||||
type
|
type
|
||||||
name
|
name
|
||||||
|
scope
|
||||||
}
|
}
|
||||||
userData {
|
userData {
|
||||||
name
|
name
|
||||||
|
@ -37,7 +37,13 @@
|
|||||||
|
|
||||||
<a target={clickable ? "_blank" : null} href={clickable ? `/monitoring/jobs/?tag=${id}` : null}>
|
<a target={clickable ? "_blank" : null} href={clickable ? `/monitoring/jobs/?tag=${id}` : null}>
|
||||||
{#if tag}
|
{#if tag}
|
||||||
<span class="badge bg-warning text-dark">{tag.type}: {tag.name}</span>
|
{#if tag?.scope === "global"}
|
||||||
|
<span style="background-color:#9e9e9e;" class="badge text-dark">{tag.type}: {tag.name}</span>
|
||||||
|
{:else if tag.scope === "admin"}
|
||||||
|
<span style="background-color:#80deea;" class="badge text-dark">{tag.type}: {tag.name}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="badge bg-warning text-dark">{tag.type}: {tag.name}</span>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
Loading...
|
Loading...
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -77,7 +77,7 @@ export function init(extraInitQuery = "") {
|
|||||||
footprint
|
footprint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tags { id, name, type }
|
tags { id, name, type, scope }
|
||||||
globalMetrics {
|
globalMetrics {
|
||||||
name
|
name
|
||||||
scope
|
scope
|
||||||
|
@ -5,6 +5,7 @@ new Job({
|
|||||||
target: document.getElementById('svelte-app'),
|
target: document.getElementById('svelte-app'),
|
||||||
props: {
|
props: {
|
||||||
dbid: jobInfos.id,
|
dbid: jobInfos.id,
|
||||||
|
username: username,
|
||||||
authlevel: authlevel,
|
authlevel: authlevel,
|
||||||
roles: roles
|
roles: roles
|
||||||
},
|
},
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
Properties:
|
Properties:
|
||||||
- `job Object`: The job object
|
- `job Object`: The job object
|
||||||
- `jobTags [Number]`: The array of currently designated tags
|
- `jobTags [Number]`: The array of currently designated tags
|
||||||
|
- `username String`: Empty string if auth. is disabled, otherwise the username as string
|
||||||
|
- `authlevel Number`: The current users authentication level
|
||||||
|
- `roles [Number]`: Enum containing available roles
|
||||||
-->
|
-->
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
@ -25,30 +28,35 @@
|
|||||||
|
|
||||||
export let job;
|
export let job;
|
||||||
export let jobTags = job.tags;
|
export let jobTags = job.tags;
|
||||||
|
export let username;
|
||||||
|
export let authlevel;
|
||||||
|
export let roles;
|
||||||
|
|
||||||
let allTags = getContext("tags"),
|
let allTags = getContext("tags"),
|
||||||
initialized = getContext("initialized");
|
initialized = getContext("initialized");
|
||||||
let newTagType = "",
|
let newTagType = "",
|
||||||
newTagName = "";
|
newTagName = "",
|
||||||
|
newTagScope = username;
|
||||||
let filterTerm = "";
|
let filterTerm = "";
|
||||||
let pendingChange = false;
|
let pendingChange = false;
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
|
||||||
const createTagMutation = ({ type, name }) => {
|
const createTagMutation = ({ type, name, scope }) => {
|
||||||
return mutationStore({
|
return mutationStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
mutation ($type: String!, $name: String!) {
|
mutation ($type: String!, $name: String!, $scope: String!) {
|
||||||
createTag(type: $type, name: $name) {
|
createTag(type: $type, name: $name, scope: $scope) {
|
||||||
id
|
id
|
||||||
type
|
type
|
||||||
name
|
name
|
||||||
|
scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: { type, name },
|
variables: { type, name, scope },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -61,6 +69,7 @@
|
|||||||
id
|
id
|
||||||
type
|
type
|
||||||
name
|
name
|
||||||
|
scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -77,6 +86,7 @@
|
|||||||
id
|
id
|
||||||
type
|
type
|
||||||
name
|
name
|
||||||
|
scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -103,9 +113,9 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTag(type, name) {
|
function createTag(type, name, scope) {
|
||||||
pendingChange = true;
|
pendingChange = true;
|
||||||
createTagMutation({ type: type, name: name }).subscribe((res) => {
|
createTagMutation({ type: type, name: name, scope: scope }).subscribe((res) => {
|
||||||
if (res.fetching === false && !res.error) {
|
if (res.fetching === false && !res.error) {
|
||||||
pendingChange = false;
|
pendingChange = false;
|
||||||
allTags = [...allTags, res.data.createTag];
|
allTags = [...allTags, res.data.createTag];
|
||||||
@ -206,16 +216,30 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<br />
|
<br />
|
||||||
{#if newTagType && newTagName && isNewTag(newTagType, newTagName)}
|
{#if newTagType && newTagName && isNewTag(newTagType, newTagName)}
|
||||||
<Button
|
<div class="d-flex">
|
||||||
outline
|
<Button
|
||||||
color="success"
|
style="margin-right: 10px;"
|
||||||
on:click={(e) => (
|
outline
|
||||||
e.preventDefault(), createTag(newTagType, newTagName)
|
color="success"
|
||||||
)}
|
on:click={(e) => (
|
||||||
>
|
e.preventDefault(), createTag(newTagType, newTagName, newTagScope)
|
||||||
Create & Add Tag:
|
)}
|
||||||
<Tag tag={{ type: newTagType, name: newTagName }} clickable={false} />
|
>
|
||||||
</Button>
|
Create & Add Tag:
|
||||||
|
<Tag tag={{ type: newTagType, name: newTagName, scope: newTagScope }} clickable={false}/>
|
||||||
|
</Button>
|
||||||
|
{#if roles && authlevel >= roles.admin}
|
||||||
|
<select
|
||||||
|
style="max-width: 175px;"
|
||||||
|
class="form-select"
|
||||||
|
bind:value={newTagScope}
|
||||||
|
>
|
||||||
|
<option value={username}>Scope: Private</option>
|
||||||
|
<option value={"global"}>Scope: Global</option>
|
||||||
|
<option value={"admin"}>Scope: Admin</option>
|
||||||
|
</select>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{:else if allTagsFiltered.length == 0}
|
{:else if allTagsFiltered.length == 0}
|
||||||
<Alert>Search Term is not a valid Tag (<code>type: name</code>)</Alert>
|
<Alert>Search Term is not a valid Tag (<code>type: name</code>)</Alert>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
id: "{{ .Infos.id }}",
|
id: "{{ .Infos.id }}",
|
||||||
};
|
};
|
||||||
const clusterCockpitConfig = {{ .Config }};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
|
const username = {{ .User.Username }};
|
||||||
const authlevel = {{ .User.GetAuthLevel }};
|
const authlevel = {{ .User.GetAuthLevel }};
|
||||||
const roles = {{ .Roles }};
|
const roles = {{ .Roles }};
|
||||||
</script>
|
</script>
|
||||||
|
@ -7,8 +7,16 @@
|
|||||||
{{ $tagType }}
|
{{ $tagType }}
|
||||||
</div>
|
</div>
|
||||||
{{ range $tagList }}
|
{{ range $tagList }}
|
||||||
<a class="btn btn-lg btn-warning" href="/monitoring/jobs/?tag={{ .id }}" role="button">
|
{{if eq .scope "global"}}
|
||||||
{{ .name }} <span class="badge bg-light text-dark">{{ .count }}</span> </a>
|
<a style="background-color:#9e9e9e;" class="btn btn-lg" href="/monitoring/jobs/?tag={{ .id }}" role="button">
|
||||||
|
{{ .name }} <span class="badge bg-light text-dark">{{ .count }}</span> </a>
|
||||||
|
{{else if eq .scope "admin"}}
|
||||||
|
<a style="background-color:#80deea;" class="btn btn-lg" href="/monitoring/jobs/?tag={{ .id }}" role="button">
|
||||||
|
{{ .name }} <span class="badge bg-light text-dark">{{ .count }}</span> </a>
|
||||||
|
{{else}}
|
||||||
|
<a class="btn btn-lg btn-warning" href="/monitoring/jobs/?tag={{ .id }}" role="button">
|
||||||
|
{{ .name }} <span class="badge bg-light text-dark">{{ .count }}</span> </a>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user