mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25:06 +01:00 
			
		
		
		
	Merge pull request #116 from ClusterCockpit/97_107_mark_and_show_shared
97 107 mark and show shared
This commit is contained in:
		api
internal
pkg/schema
web
frontend
src
templates
monitoring
@@ -27,11 +27,17 @@ type Job {
 | 
			
		||||
  state:            JobState!
 | 
			
		||||
  tags:             [Tag!]!
 | 
			
		||||
  resources:        [Resource!]!
 | 
			
		||||
  concurrentJobs:   JobLinkResultList
 | 
			
		||||
 | 
			
		||||
  metaData:         Any
 | 
			
		||||
  userData:         User
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobLink {
 | 
			
		||||
  id:               ID!
 | 
			
		||||
  jobId:            Int!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Cluster {
 | 
			
		||||
  name:         String!
 | 
			
		||||
  partitions:   [String!]!        # Slurm partitions
 | 
			
		||||
@@ -230,6 +236,12 @@ input JobFilter {
 | 
			
		||||
  memBwAvg:    FloatRange
 | 
			
		||||
  loadAvg:     FloatRange
 | 
			
		||||
  memUsedMax:  FloatRange
 | 
			
		||||
 | 
			
		||||
  exclusive:     Int
 | 
			
		||||
  sharedNode:    StringInput
 | 
			
		||||
  selfJobId:     StringInput
 | 
			
		||||
  selfStartTime: Time
 | 
			
		||||
  selfDuration:  Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input OrderByInput {
 | 
			
		||||
@@ -244,6 +256,7 @@ enum SortDirectionEnum {
 | 
			
		||||
 | 
			
		||||
input StringInput {
 | 
			
		||||
  eq:         String
 | 
			
		||||
  neq:        String
 | 
			
		||||
  contains:   String
 | 
			
		||||
  startsWith: String
 | 
			
		||||
  endsWith:   String
 | 
			
		||||
@@ -261,6 +274,11 @@ type JobResultList {
 | 
			
		||||
  count:  Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobLinkResultList {
 | 
			
		||||
  items:  [JobLink!]!
 | 
			
		||||
  count:  Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type HistoPoint {
 | 
			
		||||
  count: Int!
 | 
			
		||||
  value: Int!
 | 
			
		||||
 
 | 
			
		||||
@@ -85,6 +85,7 @@ type ComplexityRoot struct {
 | 
			
		||||
	Job struct {
 | 
			
		||||
		ArrayJobId       func(childComplexity int) int
 | 
			
		||||
		Cluster          func(childComplexity int) int
 | 
			
		||||
		ConcurrentJobs   func(childComplexity int) int
 | 
			
		||||
		Duration         func(childComplexity int) int
 | 
			
		||||
		Exclusive        func(childComplexity int) int
 | 
			
		||||
		ID               func(childComplexity int) int
 | 
			
		||||
@@ -108,6 +109,16 @@ type ComplexityRoot struct {
 | 
			
		||||
		Walltime         func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	JobLink struct {
 | 
			
		||||
		ID    func(childComplexity int) int
 | 
			
		||||
		JobID func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	JobLinkResultList struct {
 | 
			
		||||
		Count func(childComplexity int) int
 | 
			
		||||
		Items func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	JobMetric struct {
 | 
			
		||||
		Series           func(childComplexity int) int
 | 
			
		||||
		StatisticsSeries func(childComplexity int) int
 | 
			
		||||
@@ -280,6 +291,7 @@ type JobResolver interface {
 | 
			
		||||
 | 
			
		||||
	Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag, error)
 | 
			
		||||
 | 
			
		||||
	ConcurrentJobs(ctx context.Context, obj *schema.Job) (*model.JobLinkResultList, error)
 | 
			
		||||
	MetaData(ctx context.Context, obj *schema.Job) (interface{}, error)
 | 
			
		||||
	UserData(ctx context.Context, obj *schema.Job) (*model.User, error)
 | 
			
		||||
}
 | 
			
		||||
@@ -442,6 +454,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Job.Cluster(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "Job.concurrentJobs":
 | 
			
		||||
		if e.complexity.Job.ConcurrentJobs == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Job.ConcurrentJobs(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "Job.duration":
 | 
			
		||||
		if e.complexity.Job.Duration == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -589,6 +608,34 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Job.Walltime(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "JobLink.id":
 | 
			
		||||
		if e.complexity.JobLink.ID == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.JobLink.ID(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "JobLink.jobId":
 | 
			
		||||
		if e.complexity.JobLink.JobID == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.JobLink.JobID(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "JobLinkResultList.count":
 | 
			
		||||
		if e.complexity.JobLinkResultList.Count == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.JobLinkResultList.Count(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "JobLinkResultList.items":
 | 
			
		||||
		if e.complexity.JobLinkResultList.Items == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.JobLinkResultList.Items(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "JobMetric.series":
 | 
			
		||||
		if e.complexity.JobMetric.Series == nil {
 | 
			
		||||
			break
 | 
			
		||||
@@ -1468,11 +1515,17 @@ type Job {
 | 
			
		||||
  state:            JobState!
 | 
			
		||||
  tags:             [Tag!]!
 | 
			
		||||
  resources:        [Resource!]!
 | 
			
		||||
  concurrentJobs:   JobLinkResultList
 | 
			
		||||
 | 
			
		||||
  metaData:         Any
 | 
			
		||||
  userData:         User
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobLink {
 | 
			
		||||
  id:               ID!
 | 
			
		||||
  jobId:            Int!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Cluster {
 | 
			
		||||
  name:         String!
 | 
			
		||||
  partitions:   [String!]!        # Slurm partitions
 | 
			
		||||
@@ -1671,6 +1724,12 @@ input JobFilter {
 | 
			
		||||
  memBwAvg:    FloatRange
 | 
			
		||||
  loadAvg:     FloatRange
 | 
			
		||||
  memUsedMax:  FloatRange
 | 
			
		||||
 | 
			
		||||
  exclusive:     Int
 | 
			
		||||
  sharedNode:    StringInput
 | 
			
		||||
  selfJobId:     StringInput
 | 
			
		||||
  selfStartTime: Time
 | 
			
		||||
  selfDuration:  Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input OrderByInput {
 | 
			
		||||
@@ -1685,6 +1744,7 @@ enum SortDirectionEnum {
 | 
			
		||||
 | 
			
		||||
input StringInput {
 | 
			
		||||
  eq:         String
 | 
			
		||||
  neq:        String
 | 
			
		||||
  contains:   String
 | 
			
		||||
  startsWith: String
 | 
			
		||||
  endsWith:   String
 | 
			
		||||
@@ -1702,6 +1762,11 @@ type JobResultList {
 | 
			
		||||
  count:  Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobLinkResultList {
 | 
			
		||||
  items:  [JobLink!]!
 | 
			
		||||
  count:  Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type HistoPoint {
 | 
			
		||||
  count: Int!
 | 
			
		||||
  value: Int!
 | 
			
		||||
@@ -3875,6 +3940,53 @@ func (ec *executionContext) fieldContext_Job_resources(ctx context.Context, fiel
 | 
			
		||||
	return fc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _Job_concurrentJobs(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
 | 
			
		||||
	fc, err := ec.fieldContext_Job_concurrentJobs(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 ec.resolvers.Job().ConcurrentJobs(rctx, obj)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(*model.JobLinkResultList)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalOJobLinkResultList2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkResultList(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) fieldContext_Job_concurrentJobs(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
 | 
			
		||||
	fc = &graphql.FieldContext{
 | 
			
		||||
		Object:     "Job",
 | 
			
		||||
		Field:      field,
 | 
			
		||||
		IsMethod:   true,
 | 
			
		||||
		IsResolver: true,
 | 
			
		||||
		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
 | 
			
		||||
			switch field.Name {
 | 
			
		||||
			case "items":
 | 
			
		||||
				return ec.fieldContext_JobLinkResultList_items(ctx, field)
 | 
			
		||||
			case "count":
 | 
			
		||||
				return ec.fieldContext_JobLinkResultList_count(ctx, field)
 | 
			
		||||
			}
 | 
			
		||||
			return nil, fmt.Errorf("no field named %q was found under type JobLinkResultList", field.Name)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return fc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _Job_metaData(ctx context.Context, field graphql.CollectedField, obj *schema.Job) (ret graphql.Marshaler) {
 | 
			
		||||
	fc, err := ec.fieldContext_Job_metaData(ctx, field)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -3965,6 +4077,185 @@ func (ec *executionContext) fieldContext_Job_userData(ctx context.Context, field
 | 
			
		||||
	return fc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobLink_id(ctx context.Context, field graphql.CollectedField, obj *model.JobLink) (ret graphql.Marshaler) {
 | 
			
		||||
	fc, err := ec.fieldContext_JobLink_id(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.ID, 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.marshalNID2string(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) fieldContext_JobLink_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
 | 
			
		||||
	fc = &graphql.FieldContext{
 | 
			
		||||
		Object:     "JobLink",
 | 
			
		||||
		Field:      field,
 | 
			
		||||
		IsMethod:   false,
 | 
			
		||||
		IsResolver: false,
 | 
			
		||||
		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
 | 
			
		||||
			return nil, errors.New("field of type ID does not have child fields")
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return fc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobLink_jobId(ctx context.Context, field graphql.CollectedField, obj *model.JobLink) (ret graphql.Marshaler) {
 | 
			
		||||
	fc, err := ec.fieldContext_JobLink_jobId(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.JobID, 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.(int)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNInt2int(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) fieldContext_JobLink_jobId(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
 | 
			
		||||
	fc = &graphql.FieldContext{
 | 
			
		||||
		Object:     "JobLink",
 | 
			
		||||
		Field:      field,
 | 
			
		||||
		IsMethod:   false,
 | 
			
		||||
		IsResolver: false,
 | 
			
		||||
		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
 | 
			
		||||
			return nil, errors.New("field of type Int does not have child fields")
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return fc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobLinkResultList_items(ctx context.Context, field graphql.CollectedField, obj *model.JobLinkResultList) (ret graphql.Marshaler) {
 | 
			
		||||
	fc, err := ec.fieldContext_JobLinkResultList_items(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.Items, 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.([]*model.JobLink)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNJobLink2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkᚄ(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) fieldContext_JobLinkResultList_items(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
 | 
			
		||||
	fc = &graphql.FieldContext{
 | 
			
		||||
		Object:     "JobLinkResultList",
 | 
			
		||||
		Field:      field,
 | 
			
		||||
		IsMethod:   false,
 | 
			
		||||
		IsResolver: false,
 | 
			
		||||
		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
 | 
			
		||||
			switch field.Name {
 | 
			
		||||
			case "id":
 | 
			
		||||
				return ec.fieldContext_JobLink_id(ctx, field)
 | 
			
		||||
			case "jobId":
 | 
			
		||||
				return ec.fieldContext_JobLink_jobId(ctx, field)
 | 
			
		||||
			}
 | 
			
		||||
			return nil, fmt.Errorf("no field named %q was found under type JobLink", field.Name)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return fc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobLinkResultList_count(ctx context.Context, field graphql.CollectedField, obj *model.JobLinkResultList) (ret graphql.Marshaler) {
 | 
			
		||||
	fc, err := ec.fieldContext_JobLinkResultList_count(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.Count, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	if resTmp == nil {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(*int)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) fieldContext_JobLinkResultList_count(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
 | 
			
		||||
	fc = &graphql.FieldContext{
 | 
			
		||||
		Object:     "JobLinkResultList",
 | 
			
		||||
		Field:      field,
 | 
			
		||||
		IsMethod:   false,
 | 
			
		||||
		IsResolver: false,
 | 
			
		||||
		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
 | 
			
		||||
			return nil, errors.New("field of type Int does not have child fields")
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return fc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobMetric_unit(ctx context.Context, field graphql.CollectedField, obj *schema.JobMetric) (ret graphql.Marshaler) {
 | 
			
		||||
	fc, err := ec.fieldContext_JobMetric_unit(ctx, field)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -4379,6 +4670,8 @@ func (ec *executionContext) fieldContext_JobResultList_items(ctx context.Context
 | 
			
		||||
				return ec.fieldContext_Job_tags(ctx, field)
 | 
			
		||||
			case "resources":
 | 
			
		||||
				return ec.fieldContext_Job_resources(ctx, field)
 | 
			
		||||
			case "concurrentJobs":
 | 
			
		||||
				return ec.fieldContext_Job_concurrentJobs(ctx, field)
 | 
			
		||||
			case "metaData":
 | 
			
		||||
				return ec.fieldContext_Job_metaData(ctx, field)
 | 
			
		||||
			case "userData":
 | 
			
		||||
@@ -6376,6 +6669,8 @@ func (ec *executionContext) fieldContext_Query_job(ctx context.Context, field gr
 | 
			
		||||
				return ec.fieldContext_Job_tags(ctx, field)
 | 
			
		||||
			case "resources":
 | 
			
		||||
				return ec.fieldContext_Job_resources(ctx, field)
 | 
			
		||||
			case "concurrentJobs":
 | 
			
		||||
				return ec.fieldContext_Job_concurrentJobs(ctx, field)
 | 
			
		||||
			case "metaData":
 | 
			
		||||
				return ec.fieldContext_Job_metaData(ctx, field)
 | 
			
		||||
			case "userData":
 | 
			
		||||
@@ -10741,7 +11036,7 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int
 | 
			
		||||
		asMap[k] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "jobName", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax"}
 | 
			
		||||
	fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "jobName", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax", "exclusive", "sharedNode", "selfJobId", "selfStartTime", "selfDuration"}
 | 
			
		||||
	for _, k := range fieldsInOrder {
 | 
			
		||||
		v, ok := asMap[k]
 | 
			
		||||
		if !ok {
 | 
			
		||||
@@ -10900,6 +11195,46 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "exclusive":
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("exclusive"))
 | 
			
		||||
			it.Exclusive, err = ec.unmarshalOInt2ᚖint(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "sharedNode":
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sharedNode"))
 | 
			
		||||
			it.SharedNode, err = ec.unmarshalOStringInput2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐStringInput(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "selfJobId":
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("selfJobId"))
 | 
			
		||||
			it.SelfJobID, err = ec.unmarshalOStringInput2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐStringInput(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "selfStartTime":
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("selfStartTime"))
 | 
			
		||||
			it.SelfStartTime, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "selfDuration":
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("selfDuration"))
 | 
			
		||||
			it.SelfDuration, err = ec.unmarshalOInt2ᚖint(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -10989,7 +11324,7 @@ func (ec *executionContext) unmarshalInputStringInput(ctx context.Context, obj i
 | 
			
		||||
		asMap[k] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fieldsInOrder := [...]string{"eq", "contains", "startsWith", "endsWith", "in"}
 | 
			
		||||
	fieldsInOrder := [...]string{"eq", "neq", "contains", "startsWith", "endsWith", "in"}
 | 
			
		||||
	for _, k := range fieldsInOrder {
 | 
			
		||||
		v, ok := asMap[k]
 | 
			
		||||
		if !ok {
 | 
			
		||||
@@ -11004,6 +11339,14 @@ func (ec *executionContext) unmarshalInputStringInput(ctx context.Context, obj i
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "neq":
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("neq"))
 | 
			
		||||
			it.Neq, err = ec.unmarshalOString2ᚖstring(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "contains":
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
@@ -11510,6 +11853,23 @@ func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				atomic.AddUint32(&invalids, 1)
 | 
			
		||||
			}
 | 
			
		||||
		case "concurrentJobs":
 | 
			
		||||
			field := field
 | 
			
		||||
 | 
			
		||||
			innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
 | 
			
		||||
				defer func() {
 | 
			
		||||
					if r := recover(); r != nil {
 | 
			
		||||
						ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				res = ec._Job_concurrentJobs(ctx, field, obj)
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			out.Concurrently(i, func() graphql.Marshaler {
 | 
			
		||||
				return innerFunc(ctx)
 | 
			
		||||
 | 
			
		||||
			})
 | 
			
		||||
		case "metaData":
 | 
			
		||||
			field := field
 | 
			
		||||
 | 
			
		||||
@@ -11555,6 +11915,73 @@ func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var jobLinkImplementors = []string{"JobLink"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobLink(ctx context.Context, sel ast.SelectionSet, obj *model.JobLink) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, jobLinkImplementors)
 | 
			
		||||
	out := graphql.NewFieldSet(fields)
 | 
			
		||||
	var invalids uint32
 | 
			
		||||
	for i, field := range fields {
 | 
			
		||||
		switch field.Name {
 | 
			
		||||
		case "__typename":
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("JobLink")
 | 
			
		||||
		case "id":
 | 
			
		||||
 | 
			
		||||
			out.Values[i] = ec._JobLink_id(ctx, field, obj)
 | 
			
		||||
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "jobId":
 | 
			
		||||
 | 
			
		||||
			out.Values[i] = ec._JobLink_jobId(ctx, field, obj)
 | 
			
		||||
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unknown field " + strconv.Quote(field.Name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	out.Dispatch()
 | 
			
		||||
	if invalids > 0 {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var jobLinkResultListImplementors = []string{"JobLinkResultList"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobLinkResultList(ctx context.Context, sel ast.SelectionSet, obj *model.JobLinkResultList) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, jobLinkResultListImplementors)
 | 
			
		||||
	out := graphql.NewFieldSet(fields)
 | 
			
		||||
	var invalids uint32
 | 
			
		||||
	for i, field := range fields {
 | 
			
		||||
		switch field.Name {
 | 
			
		||||
		case "__typename":
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("JobLinkResultList")
 | 
			
		||||
		case "items":
 | 
			
		||||
 | 
			
		||||
			out.Values[i] = ec._JobLinkResultList_items(ctx, field, obj)
 | 
			
		||||
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "count":
 | 
			
		||||
 | 
			
		||||
			out.Values[i] = ec._JobLinkResultList_count(ctx, field, obj)
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			panic("unknown field " + strconv.Quote(field.Name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	out.Dispatch()
 | 
			
		||||
	if invalids > 0 {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var jobMetricImplementors = []string{"JobMetric"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _JobMetric(ctx context.Context, sel ast.SelectionSet, obj *schema.JobMetric) graphql.Marshaler {
 | 
			
		||||
@@ -13686,6 +14113,60 @@ func (ec *executionContext) unmarshalNJobFilter2ᚖgithubᚗcomᚋClusterCockpit
 | 
			
		||||
	return &res, graphql.ErrorOnPath(ctx, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNJobLink2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.JobLink) graphql.Marshaler {
 | 
			
		||||
	ret := make(graphql.Array, len(v))
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	isLen1 := len(v) == 1
 | 
			
		||||
	if !isLen1 {
 | 
			
		||||
		wg.Add(len(v))
 | 
			
		||||
	}
 | 
			
		||||
	for i := range v {
 | 
			
		||||
		i := i
 | 
			
		||||
		fc := &graphql.FieldContext{
 | 
			
		||||
			Index:  &i,
 | 
			
		||||
			Result: &v[i],
 | 
			
		||||
		}
 | 
			
		||||
		ctx := graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
		f := func(i int) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				if r := recover(); r != nil {
 | 
			
		||||
					ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
					ret = nil
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
			if !isLen1 {
 | 
			
		||||
				defer wg.Done()
 | 
			
		||||
			}
 | 
			
		||||
			ret[i] = ec.marshalNJobLink2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLink(ctx, sel, v[i])
 | 
			
		||||
		}
 | 
			
		||||
		if isLen1 {
 | 
			
		||||
			f(i)
 | 
			
		||||
		} else {
 | 
			
		||||
			go f(i)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
 | 
			
		||||
	for _, e := range ret {
 | 
			
		||||
		if e == graphql.Null {
 | 
			
		||||
			return graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNJobLink2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLink(ctx context.Context, sel ast.SelectionSet, v *model.JobLink) graphql.Marshaler {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
 | 
			
		||||
			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return ec._JobLink(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNJobMetric2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋpkgᚋschemaᚐJobMetric(ctx context.Context, sel ast.SelectionSet, v *schema.JobMetric) graphql.Marshaler {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
 | 
			
		||||
@@ -14944,6 +15425,13 @@ func (ec *executionContext) unmarshalOJobFilter2ᚖgithubᚗcomᚋClusterCockpit
 | 
			
		||||
	return &res, graphql.ErrorOnPath(ctx, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalOJobLinkResultList2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐJobLinkResultList(ctx context.Context, sel ast.SelectionSet, v *model.JobLinkResultList) graphql.Marshaler {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	return ec._JobLinkResultList(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalOJobState2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋpkgᚋschemaᚐJobStateᚄ(ctx context.Context, v interface{}) ([]schema.JobState, error) {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,21 @@ type JobFilter struct {
 | 
			
		||||
	MemBwAvg        *FloatRange       `json:"memBwAvg"`
 | 
			
		||||
	LoadAvg         *FloatRange       `json:"loadAvg"`
 | 
			
		||||
	MemUsedMax      *FloatRange       `json:"memUsedMax"`
 | 
			
		||||
	Exclusive       *int              `json:"exclusive"`
 | 
			
		||||
	SharedNode      *StringInput      `json:"sharedNode"`
 | 
			
		||||
	SelfJobID       *StringInput      `json:"selfJobId"`
 | 
			
		||||
	SelfStartTime   *time.Time        `json:"selfStartTime"`
 | 
			
		||||
	SelfDuration    *int              `json:"selfDuration"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobLink struct {
 | 
			
		||||
	ID    string `json:"id"`
 | 
			
		||||
	JobID int    `json:"jobId"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobLinkResultList struct {
 | 
			
		||||
	Items []*JobLink `json:"items"`
 | 
			
		||||
	Count *int       `json:"count"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobMetricWithName struct {
 | 
			
		||||
@@ -105,6 +120,7 @@ type PageRequest struct {
 | 
			
		||||
 | 
			
		||||
type StringInput struct {
 | 
			
		||||
	Eq         *string  `json:"eq"`
 | 
			
		||||
	Neq        *string  `json:"neq"`
 | 
			
		||||
	Contains   *string  `json:"contains"`
 | 
			
		||||
	StartsWith *string  `json:"startsWith"`
 | 
			
		||||
	EndsWith   *string  `json:"endsWith"`
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,39 @@ func (r *jobResolver) Tags(ctx context.Context, obj *schema.Job) ([]*schema.Tag,
 | 
			
		||||
	return r.Repo.GetTags(&obj.ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConcurrentJobs is the resolver for the concurrentJobs field.
 | 
			
		||||
func (r *jobResolver) ConcurrentJobs(ctx context.Context, obj *schema.Job) (*model.JobLinkResultList, error) {
 | 
			
		||||
 | 
			
		||||
	exc := int(obj.Exclusive)
 | 
			
		||||
	if exc != 1 {
 | 
			
		||||
		filter := []*model.JobFilter{}
 | 
			
		||||
		jid := fmt.Sprint(obj.JobID)
 | 
			
		||||
		jdu := int(obj.Duration)
 | 
			
		||||
		filter = append(filter, &model.JobFilter{Exclusive: &exc})
 | 
			
		||||
		filter = append(filter, &model.JobFilter{SharedNode: &model.StringInput{Contains: &obj.Resources[0].Hostname}})
 | 
			
		||||
		filter = append(filter, &model.JobFilter{SelfJobID: &model.StringInput{Neq: &jid}})
 | 
			
		||||
		filter = append(filter, &model.JobFilter{SelfStartTime: &obj.StartTime, SelfDuration: &jdu})
 | 
			
		||||
 | 
			
		||||
		jobLinks, err := r.Repo.QueryJobLinks(ctx, filter)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn("Error while querying jobLinks")
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		count, err := r.Repo.CountJobs(ctx, filter)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn("Error while counting jobLinks")
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result := &model.JobLinkResultList{Items: jobLinks, Count: &count}
 | 
			
		||||
 | 
			
		||||
		return result, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MetaData is the resolver for the metaData field.
 | 
			
		||||
func (r *jobResolver) MetaData(ctx context.Context, obj *schema.Job) (interface{}, error) {
 | 
			
		||||
	return r.Repo.FetchMetadata(obj)
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ func scanJob(row interface{ Scan(...interface{}) error }) (*schema.Job, error) {
 | 
			
		||||
		&job.ID, &job.JobID, &job.User, &job.Project, &job.Cluster, &job.SubCluster, &job.StartTimeUnix, &job.Partition, &job.ArrayJobId,
 | 
			
		||||
		&job.NumNodes, &job.NumHWThreads, &job.NumAcc, &job.Exclusive, &job.MonitoringStatus, &job.SMT, &job.State,
 | 
			
		||||
		&job.Duration, &job.Walltime, &job.RawResources /*&job.RawMetaData*/); err != nil {
 | 
			
		||||
		log.Warnf("Error while scanning rows: %v", err)
 | 
			
		||||
		log.Warnf("Error while scanning rows (Job): %v", err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -140,6 +140,17 @@ func (r *JobRepository) Flush() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanJobLink(row interface{ Scan(...interface{}) error }) (*model.JobLink, error) {
 | 
			
		||||
	jobLink := &model.JobLink{}
 | 
			
		||||
	if err := row.Scan(
 | 
			
		||||
		&jobLink.ID, &jobLink.JobID); err != nil {
 | 
			
		||||
		log.Warn("Error while scanning rows (jobLink)")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return jobLink, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *JobRepository) FetchJobName(job *schema.Job) (*string, error) {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	cachekey := fmt.Sprintf("metadata:%d", job.ID)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import (
 | 
			
		||||
	sq "github.com/Masterminds/squirrel"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SecurityCheck-less, private: Returns a list of jobs matching the provided filters. page and order are optional-
 | 
			
		||||
func (r *JobRepository) queryJobs(
 | 
			
		||||
	query sq.SelectBuilder,
 | 
			
		||||
	filters []*model.JobFilter,
 | 
			
		||||
@@ -65,7 +66,7 @@ func (r *JobRepository) queryJobs(
 | 
			
		||||
		job, err := scanJob(rows)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			rows.Close()
 | 
			
		||||
			log.Warn("Error while scanning rows")
 | 
			
		||||
			log.Warn("Error while scanning rows (Jobs)")
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		jobs = append(jobs, job)
 | 
			
		||||
@@ -74,6 +75,7 @@ func (r *JobRepository) queryJobs(
 | 
			
		||||
	return jobs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// testFunction for queryJobs
 | 
			
		||||
func (r *JobRepository) testQueryJobs(
 | 
			
		||||
	filters []*model.JobFilter,
 | 
			
		||||
	page *model.PageRequest,
 | 
			
		||||
@@ -83,7 +85,7 @@ func (r *JobRepository) testQueryJobs(
 | 
			
		||||
		filters, page, order)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// QueryJobs returns a list of jobs matching the provided filters. page and order are optional-
 | 
			
		||||
// Public function with added securityCheck, calls private queryJobs function above
 | 
			
		||||
func (r *JobRepository) QueryJobs(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	filters []*model.JobFilter,
 | 
			
		||||
@@ -100,6 +102,63 @@ func (r *JobRepository) QueryJobs(
 | 
			
		||||
		filters, page, order)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SecurityCheck-less, private: returns a list of minimal job information (DB-ID and jobId) of shared jobs for link-building based the provided filters.
 | 
			
		||||
func (r *JobRepository) queryJobLinks(
 | 
			
		||||
	query sq.SelectBuilder,
 | 
			
		||||
	filters []*model.JobFilter) ([]*model.JobLink, error) {
 | 
			
		||||
 | 
			
		||||
	for _, f := range filters {
 | 
			
		||||
		query = BuildWhereClause(f, query)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sql, args, err := query.ToSql()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Warn("Error while converting query to sql")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Debugf("SQL query: `%s`, args: %#v", sql, args)
 | 
			
		||||
	rows, err := query.RunWith(r.stmtCache).Query()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Error while running query")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jobLinks := make([]*model.JobLink, 0, 50)
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		jobLink, err := scanJobLink(rows)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			rows.Close()
 | 
			
		||||
			log.Warn("Error while scanning rows (JobLinks)")
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		jobLinks = append(jobLinks, jobLink)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return jobLinks, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// testFunction for queryJobLinks
 | 
			
		||||
func (r *JobRepository) testQueryJobLinks(
 | 
			
		||||
	filters []*model.JobFilter) ([]*model.JobLink, error) {
 | 
			
		||||
 | 
			
		||||
	return r.queryJobLinks(sq.Select(jobColumns...).From("job"), filters)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *JobRepository) QueryJobLinks(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	filters []*model.JobFilter) ([]*model.JobLink, error) {
 | 
			
		||||
 | 
			
		||||
	query, qerr := SecurityCheck(ctx, sq.Select("job.id", "job.job_id").From("job"))
 | 
			
		||||
 | 
			
		||||
	if qerr != nil {
 | 
			
		||||
		return nil, qerr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.queryJobLinks(query, filters)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SecurityCheck-less, private: Returns the number of jobs matching the filters
 | 
			
		||||
func (r *JobRepository) countJobs(query sq.SelectBuilder,
 | 
			
		||||
	filters []*model.JobFilter) (int, error) {
 | 
			
		||||
 | 
			
		||||
@@ -122,12 +181,14 @@ func (r *JobRepository) countJobs(query sq.SelectBuilder,
 | 
			
		||||
	return count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// testFunction for countJobs
 | 
			
		||||
func (r *JobRepository) testCountJobs(
 | 
			
		||||
	filters []*model.JobFilter) (int, error) {
 | 
			
		||||
 | 
			
		||||
	return r.countJobs(sq.Select("count(*)").From("job"), filters)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Public function with added securityCheck, calls private countJobs function above
 | 
			
		||||
func (r *JobRepository) CountJobs(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	filters []*model.JobFilter) (int, error) {
 | 
			
		||||
@@ -226,6 +287,21 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select
 | 
			
		||||
	if filter.MemUsedMax != nil {
 | 
			
		||||
		query = buildFloatCondition("job.mem_used_max", filter.MemUsedMax, query)
 | 
			
		||||
	}
 | 
			
		||||
	// Shared Jobs Query
 | 
			
		||||
	if filter.Exclusive != nil {
 | 
			
		||||
		query = query.Where("job.exclusive = ?", *filter.Exclusive)
 | 
			
		||||
	}
 | 
			
		||||
	if filter.SharedNode != nil {
 | 
			
		||||
		query = buildStringCondition("job.resources", filter.SharedNode, query)
 | 
			
		||||
	}
 | 
			
		||||
	if filter.SelfJobID != nil {
 | 
			
		||||
		query = buildStringCondition("job.job_id", filter.SelfJobID, query)
 | 
			
		||||
	}
 | 
			
		||||
	if filter.SelfStartTime != nil && filter.SelfDuration != nil {
 | 
			
		||||
		start := filter.SelfStartTime.Unix() + 10 // There does not seem to be a portable way to get the current unix timestamp accross different DBs.
 | 
			
		||||
		end := start + int64(*filter.SelfDuration) - 20
 | 
			
		||||
		query = query.Where("((job.start_time BETWEEN ? AND ?) OR ((job.start_time + job.duration) BETWEEN ? AND ?))", start, end, start, end)
 | 
			
		||||
	}
 | 
			
		||||
	return query
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -253,6 +329,9 @@ func buildStringCondition(field string, cond *model.StringInput, query sq.Select
 | 
			
		||||
	if cond.Eq != nil {
 | 
			
		||||
		return query.Where(field+" = ?", *cond.Eq)
 | 
			
		||||
	}
 | 
			
		||||
	if cond.Neq != nil {
 | 
			
		||||
		return query.Where(field+" != ?", *cond.Neq)
 | 
			
		||||
	}
 | 
			
		||||
	if cond.StartsWith != nil {
 | 
			
		||||
		return query.Where(field+" LIKE ?", fmt.Sprint(*cond.StartsWith, "%"))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ type BaseJob struct {
 | 
			
		||||
	Resources        []*Resource       `json:"resources"`                                                                                                    // Resources used by job
 | 
			
		||||
	RawMetaData      []byte            `json:"-" db:"meta_data"`                                                                                             // Additional information about the job [As Bytes]
 | 
			
		||||
	MetaData         map[string]string `json:"metaData"`                                                                                                     // Additional information about the job
 | 
			
		||||
	ConcurrentJobs   JobLinkResultList `json:"concurrentJobs"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Job struct type
 | 
			
		||||
@@ -72,6 +73,17 @@ type Job struct {
 | 
			
		||||
//	*int64 `json:"id,omitempty"` >> never used in the job-archive, only
 | 
			
		||||
//	available via REST-API
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
type JobLink struct {
 | 
			
		||||
	ID    int64 `json:"id"`
 | 
			
		||||
	JobID int64 `json:"jobId"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JobLinkResultList struct {
 | 
			
		||||
	Items []*JobLink `json:"items"`
 | 
			
		||||
	Count int        `json:"count"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JobMeta model
 | 
			
		||||
// @Description Meta data information of a HPC job.
 | 
			
		||||
type JobMeta struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@
 | 
			
		||||
    import { getContext } from 'svelte'
 | 
			
		||||
 | 
			
		||||
    export let dbid
 | 
			
		||||
    export let authlevel
 | 
			
		||||
    export let roles
 | 
			
		||||
 | 
			
		||||
    const { query: initq } = init(`
 | 
			
		||||
        job(id: "${dbid}") {
 | 
			
		||||
@@ -23,8 +25,9 @@
 | 
			
		||||
            monitoringStatus, state, walltime,
 | 
			
		||||
            tags { id, type, name },
 | 
			
		||||
            resources { hostname, hwthreads, accelerators },
 | 
			
		||||
            metaData
 | 
			
		||||
            userData { name, email }
 | 
			
		||||
            metaData,
 | 
			
		||||
            userData { name, email },
 | 
			
		||||
            concurrentJobs { items { id, jobId }, count }
 | 
			
		||||
        }
 | 
			
		||||
    `)
 | 
			
		||||
 | 
			
		||||
@@ -101,6 +104,23 @@
 | 
			
		||||
        {/if}
 | 
			
		||||
    </Col>
 | 
			
		||||
    {#if $jobMetrics.data && $initq.data}
 | 
			
		||||
        {#if $initq.data.job.concurrentJobs != null}
 | 
			
		||||
            {#if authlevel > roles.manager}
 | 
			
		||||
                <Col>
 | 
			
		||||
                    <h5>Concurrent Jobs <Icon name="info-circle" style="cursor:help;" title="Shared jobs running on the same node with overlapping runtimes"/></h5>
 | 
			
		||||
                    <ul>
 | 
			
		||||
                    {#each $initq.data.job.concurrentJobs.items as pjob, index}
 | 
			
		||||
                        <li><a href="/monitoring/job/{pjob.id}" target="_blank">{pjob.jobId}</a></li>
 | 
			
		||||
                    {/each}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </Col>
 | 
			
		||||
            {:else}
 | 
			
		||||
                <Col>
 | 
			
		||||
                    <h5>{$initq.data.job.concurrentJobs.items.length} Concurrent Jobs</h5>
 | 
			
		||||
                    <p>Number of shared jobs on the same node with overlapping runtimes.</p>
 | 
			
		||||
                </Col>
 | 
			
		||||
            {/if}
 | 
			
		||||
        {/if}
 | 
			
		||||
        <Col>
 | 
			
		||||
            <PolarPlot
 | 
			
		||||
                width={polarPlotSize} height={polarPlotSize}
 | 
			
		||||
@@ -166,7 +186,8 @@
 | 
			
		||||
                        metricName={item.metric}
 | 
			
		||||
                        rawData={item.data.map(x => x.metric)}
 | 
			
		||||
                        scopes={item.data.map(x => x.scope)}
 | 
			
		||||
                        width={width}/>
 | 
			
		||||
                        width={width}
 | 
			
		||||
                        isShared={($initq.data.job.exclusive != 1)}/>
 | 
			
		||||
                {:else}
 | 
			
		||||
                    <Card body color="warning">No data for <code>{item.metric}</code></Card>
 | 
			
		||||
                {/if}
 | 
			
		||||
@@ -250,4 +271,10 @@
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        padding: 5px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ul {
 | 
			
		||||
        columns: 2;
 | 
			
		||||
        -webkit-columns: 2;
 | 
			
		||||
        -moz-columns: 2;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
    export let scopes
 | 
			
		||||
    export let width
 | 
			
		||||
    export let rawData
 | 
			
		||||
    export let isShared = false
 | 
			
		||||
 | 
			
		||||
    const dispatch = createEventDispatcher()
 | 
			
		||||
    const cluster = getContext('clusters').find(cluster => cluster.name == job.cluster)
 | 
			
		||||
@@ -87,6 +88,7 @@
 | 
			
		||||
            cluster={cluster} subCluster={subCluster}
 | 
			
		||||
            timestep={data.timestep}
 | 
			
		||||
            scope={selectedScope} metric={metricName}
 | 
			
		||||
            series={series} />
 | 
			
		||||
            series={series}
 | 
			
		||||
            isShared={isShared} />
 | 
			
		||||
    {/if}
 | 
			
		||||
{/key}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@ import Job from './Job.root.svelte'
 | 
			
		||||
new Job({
 | 
			
		||||
    target: document.getElementById('svelte-app'),
 | 
			
		||||
    props: {
 | 
			
		||||
        dbid: jobInfos.id
 | 
			
		||||
        dbid: jobInfos.id,
 | 
			
		||||
        authlevel: authlevel,
 | 
			
		||||
        roles: roles
 | 
			
		||||
    },
 | 
			
		||||
    context: new Map([
 | 
			
		||||
            ['cc-config', clusterCockpitConfig]
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
        const seconds = duration;
 | 
			
		||||
        return `${hours}:${('0' + minutes).slice(-2)}:${('0' + seconds).slice(-2)}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
 
 | 
			
		||||
@@ -174,6 +174,7 @@
 | 
			
		||||
                        metric={metric.data.name}
 | 
			
		||||
                        {cluster}
 | 
			
		||||
                        subCluster={job.subCluster}
 | 
			
		||||
                        isShared={(job.exclusive != 1)}
 | 
			
		||||
                    />
 | 
			
		||||
                {:else if metric.removed == true && metric.data == null}
 | 
			
		||||
                    <Card body color="info"
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@
 | 
			
		||||
    export let metric
 | 
			
		||||
    export let useStatsSeries = null
 | 
			
		||||
    export let scope = 'node'
 | 
			
		||||
    export let isShared = false
 | 
			
		||||
 | 
			
		||||
    if (useStatsSeries == null)
 | 
			
		||||
        useStatsSeries = statisticsSeries != null
 | 
			
		||||
@@ -142,13 +143,17 @@
 | 
			
		||||
        hooks: {
 | 
			
		||||
            draw: [(u) => {
 | 
			
		||||
                // Draw plot type label:
 | 
			
		||||
                let text = `${scope}${plotSeries.length > 2 ? 's' : ''}${
 | 
			
		||||
                let textl = `${scope}${plotSeries.length > 2 ? 's' : ''}${
 | 
			
		||||
                    useStatsSeries ? ': min/avg/max' : (metricConfig != null && scope != metricConfig.scope ? ` (${metricConfig.aggregation})` : '')}`
 | 
			
		||||
                let textr = `${(isShared && (scope != 'core' && scope != 'accelerator')) ? '[Shared]' : '' }`
 | 
			
		||||
                u.ctx.save()
 | 
			
		||||
                u.ctx.textAlign = 'start' // 'end'
 | 
			
		||||
                u.ctx.fillStyle = 'black'
 | 
			
		||||
                u.ctx.fillText(text, u.bbox.left + 10, u.bbox.top + 10)
 | 
			
		||||
                // u.ctx.fillText(text, u.bbox.left + u.bbox.width - 10, u.bbox.top + u.bbox.height - 10)
 | 
			
		||||
                u.ctx.fillText(textl, u.bbox.left + 10, u.bbox.top + 10)
 | 
			
		||||
                u.ctx.textAlign = 'end'
 | 
			
		||||
                u.ctx.fillStyle = 'black'
 | 
			
		||||
                u.ctx.fillText(textr, u.bbox.left + u.bbox.width - 10, u.bbox.top + 10)
 | 
			
		||||
                // u.ctx.fillText(text, u.bbox.left + u.bbox.width - 10, u.bbox.top + u.bbox.height - 10) // Recipe for bottom right
 | 
			
		||||
 | 
			
		||||
                if (!thresholds) {
 | 
			
		||||
                    u.ctx.restore()
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,8 @@
 | 
			
		||||
            clusterId: "{{ .Infos.clusterId }}"
 | 
			
		||||
        };
 | 
			
		||||
        const clusterCockpitConfig = {{ .Config }};
 | 
			
		||||
        const authlevel = {{ .User.GetAuthLevel }};
 | 
			
		||||
        const roles = {{ .Roles }};
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src='/build/job.js'></script>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user