diff --git a/api/schema.graphqls b/api/schema.graphqls index 8f5e1c7..1c81e6b 100644 --- a/api/schema.graphqls +++ b/api/schema.graphqls @@ -164,6 +164,13 @@ type JobMetricWithName { metric: JobMetric! } +type ClusterMetricWithName { + name: String! + unit: Unit + timestep: Int! + data: [NullableFloat!]! +} + type JobMetric { unit: Unit timestep: Int! @@ -267,6 +274,11 @@ type NodeMetrics { metrics: [JobMetricWithName!]! } +type ClusterMetrics { + nodeCount: Int! + metrics: [ClusterMetricWithName!]! +} + type NodesResultList { items: [NodeMetrics!]! offset: Int @@ -385,6 +397,13 @@ type Query { page: PageRequest resolution: Int ): NodesResultList! + + clusterMetrics( + cluster: String! + metrics: [String!] + from: Time! + to: Time! + ): ClusterMetrics! } type Mutation { diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index a3b1a1d..b148942 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -66,6 +66,18 @@ type ComplexityRoot struct { SubClusters func(childComplexity int) int } + ClusterMetricWithName struct { + Data func(childComplexity int) int + Name func(childComplexity int) int + Timestep func(childComplexity int) int + Unit func(childComplexity int) int + } + + ClusterMetrics struct { + Metrics func(childComplexity int) int + NodeCount func(childComplexity int) int + } + ClusterSupport struct { Cluster func(childComplexity int) int SubClusters func(childComplexity int) int @@ -319,6 +331,7 @@ type ComplexityRoot struct { Query struct { AllocatedNodes func(childComplexity int, cluster string) int + ClusterMetrics func(childComplexity int, cluster string, metrics []string, from time.Time, to time.Time) int Clusters func(childComplexity int) int GlobalMetrics func(childComplexity int) int Job func(childComplexity int, id string) int @@ -485,6 +498,7 @@ type QueryResolver interface { RooflineHeatmap(ctx context.Context, filter []*model.JobFilter, rows int, cols int, minX float64, minY float64, maxX float64, maxY float64) ([][]float64, error) NodeMetrics(ctx context.Context, cluster string, nodes []string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time) ([]*model.NodeMetrics, error) NodeMetricsList(ctx context.Context, cluster string, subCluster string, stateFilter string, nodeFilter string, scopes []schema.MetricScope, metrics []string, from time.Time, to time.Time, page *model.PageRequest, resolution *int) (*model.NodesResultList, error) + ClusterMetrics(ctx context.Context, cluster string, metrics []string, from time.Time, to time.Time) (*model.ClusterMetrics, error) } type SubClusterResolver interface { NumberOfNodes(ctx context.Context, obj *schema.SubCluster) (int, error) @@ -551,6 +565,48 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Cluster.SubClusters(childComplexity), true + case "ClusterMetricWithName.data": + if e.complexity.ClusterMetricWithName.Data == nil { + break + } + + return e.complexity.ClusterMetricWithName.Data(childComplexity), true + + case "ClusterMetricWithName.name": + if e.complexity.ClusterMetricWithName.Name == nil { + break + } + + return e.complexity.ClusterMetricWithName.Name(childComplexity), true + + case "ClusterMetricWithName.timestep": + if e.complexity.ClusterMetricWithName.Timestep == nil { + break + } + + return e.complexity.ClusterMetricWithName.Timestep(childComplexity), true + + case "ClusterMetricWithName.unit": + if e.complexity.ClusterMetricWithName.Unit == nil { + break + } + + return e.complexity.ClusterMetricWithName.Unit(childComplexity), true + + case "ClusterMetrics.metrics": + if e.complexity.ClusterMetrics.Metrics == nil { + break + } + + return e.complexity.ClusterMetrics.Metrics(childComplexity), true + + case "ClusterMetrics.nodeCount": + if e.complexity.ClusterMetrics.NodeCount == nil { + break + } + + return e.complexity.ClusterMetrics.NodeCount(childComplexity), true + case "ClusterSupport.cluster": if e.complexity.ClusterSupport.Cluster == nil { break @@ -1699,6 +1755,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Query.AllocatedNodes(childComplexity, args["cluster"].(string)), true + case "Query.clusterMetrics": + if e.complexity.Query.ClusterMetrics == nil { + break + } + + args, err := ec.field_Query_clusterMetrics_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.ClusterMetrics(childComplexity, args["cluster"].(string), args["metrics"].([]string), args["from"].(time.Time), args["to"].(time.Time)), true + case "Query.clusters": if e.complexity.Query.Clusters == nil { break @@ -2577,6 +2645,13 @@ type JobMetricWithName { metric: JobMetric! } +type ClusterMetricWithName { + name: String! + unit: Unit + timestep: Int! + data: [NullableFloat!]! +} + type JobMetric { unit: Unit timestep: Int! @@ -2680,6 +2755,11 @@ type NodeMetrics { metrics: [JobMetricWithName!]! } +type ClusterMetrics { + nodeCount: Int! + metrics: [ClusterMetricWithName!]! +} + type NodesResultList { items: [NodeMetrics!]! offset: Int @@ -2798,6 +2878,13 @@ type Query { page: PageRequest resolution: Int ): NodesResultList! + + clusterMetrics( + cluster: String! + metrics: [String!] + from: Time! + to: Time! + ): ClusterMetrics! } type Mutation { @@ -3074,6 +3161,32 @@ func (ec *executionContext) field_Query_allocatedNodes_args(ctx context.Context, return args, nil } +func (ec *executionContext) field_Query_clusterMetrics_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "cluster", ec.unmarshalNString2string) + if err != nil { + return nil, err + } + args["cluster"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "metrics", ec.unmarshalOString2ᚕstringᚄ) + if err != nil { + return nil, err + } + args["metrics"] = arg1 + arg2, err := graphql.ProcessArgField(ctx, rawArgs, "from", ec.unmarshalNTime2timeᚐTime) + if err != nil { + return nil, err + } + args["from"] = arg2 + arg3, err := graphql.ProcessArgField(ctx, rawArgs, "to", ec.unmarshalNTime2timeᚐTime) + if err != nil { + return nil, err + } + args["to"] = arg3 + return args, nil +} + func (ec *executionContext) field_Query_jobMetrics_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -3784,6 +3897,283 @@ func (ec *executionContext) fieldContext_Cluster_subClusters(_ context.Context, return fc, nil } +func (ec *executionContext) _ClusterMetricWithName_name(ctx context.Context, field graphql.CollectedField, obj *model.ClusterMetricWithName) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ClusterMetricWithName_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, 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_ClusterMetricWithName_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ClusterMetricWithName", + 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) _ClusterMetricWithName_unit(ctx context.Context, field graphql.CollectedField, obj *model.ClusterMetricWithName) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ClusterMetricWithName_unit(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Unit, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*schema.Unit) + fc.Result = res + return ec.marshalOUnit2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐUnit(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ClusterMetricWithName_unit(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ClusterMetricWithName", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "base": + return ec.fieldContext_Unit_base(ctx, field) + case "prefix": + return ec.fieldContext_Unit_prefix(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Unit", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _ClusterMetricWithName_timestep(ctx context.Context, field graphql.CollectedField, obj *model.ClusterMetricWithName) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ClusterMetricWithName_timestep(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Timestep, 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_ClusterMetricWithName_timestep(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ClusterMetricWithName", + 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) _ClusterMetricWithName_data(ctx context.Context, field graphql.CollectedField, obj *model.ClusterMetricWithName) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ClusterMetricWithName_data(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Data, 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.([]schema.Float) + fc.Result = res + return ec.marshalNNullableFloat2ᚕgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐFloatᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ClusterMetricWithName_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ClusterMetricWithName", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type NullableFloat does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ClusterMetrics_nodeCount(ctx context.Context, field graphql.CollectedField, obj *model.ClusterMetrics) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ClusterMetrics_nodeCount(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.NodeCount, 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_ClusterMetrics_nodeCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ClusterMetrics", + 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) _ClusterMetrics_metrics(ctx context.Context, field graphql.CollectedField, obj *model.ClusterMetrics) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ClusterMetrics_metrics(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Metrics, 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.ClusterMetricWithName) + fc.Result = res + return ec.marshalNClusterMetricWithName2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐClusterMetricWithNameᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ClusterMetrics_metrics(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ClusterMetrics", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext_ClusterMetricWithName_name(ctx, field) + case "unit": + return ec.fieldContext_ClusterMetricWithName_unit(ctx, field) + case "timestep": + return ec.fieldContext_ClusterMetricWithName_timestep(ctx, field) + case "data": + return ec.fieldContext_ClusterMetricWithName_data(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ClusterMetricWithName", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _ClusterSupport_cluster(ctx context.Context, field graphql.CollectedField, obj *schema.ClusterSupport) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ClusterSupport_cluster(ctx, field) if err != nil { @@ -12353,6 +12743,67 @@ func (ec *executionContext) fieldContext_Query_nodeMetricsList(ctx context.Conte return fc, nil } +func (ec *executionContext) _Query_clusterMetrics(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_clusterMetrics(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().ClusterMetrics(rctx, fc.Args["cluster"].(string), fc.Args["metrics"].([]string), fc.Args["from"].(time.Time), fc.Args["to"].(time.Time)) + }) + 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.ClusterMetrics) + fc.Result = res + return ec.marshalNClusterMetrics2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐClusterMetrics(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_clusterMetrics(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "nodeCount": + return ec.fieldContext_ClusterMetrics_nodeCount(ctx, field) + case "metrics": + return ec.fieldContext_ClusterMetrics_metrics(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ClusterMetrics", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_clusterMetrics_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query___type(ctx, field) if err != nil { @@ -17527,6 +17978,101 @@ func (ec *executionContext) _Cluster(ctx context.Context, sel ast.SelectionSet, return out } +var clusterMetricWithNameImplementors = []string{"ClusterMetricWithName"} + +func (ec *executionContext) _ClusterMetricWithName(ctx context.Context, sel ast.SelectionSet, obj *model.ClusterMetricWithName) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, clusterMetricWithNameImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ClusterMetricWithName") + case "name": + out.Values[i] = ec._ClusterMetricWithName_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "unit": + out.Values[i] = ec._ClusterMetricWithName_unit(ctx, field, obj) + case "timestep": + out.Values[i] = ec._ClusterMetricWithName_timestep(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "data": + out.Values[i] = ec._ClusterMetricWithName_data(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var clusterMetricsImplementors = []string{"ClusterMetrics"} + +func (ec *executionContext) _ClusterMetrics(ctx context.Context, sel ast.SelectionSet, obj *model.ClusterMetrics) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, clusterMetricsImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ClusterMetrics") + case "nodeCount": + out.Values[i] = ec._ClusterMetrics_nodeCount(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "metrics": + out.Values[i] = ec._ClusterMetrics_metrics(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var clusterSupportImplementors = []string{"ClusterSupport"} func (ec *executionContext) _ClusterSupport(ctx context.Context, sel ast.SelectionSet, obj *schema.ClusterSupport) graphql.Marshaler { @@ -20101,6 +20647,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "clusterMetrics": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_clusterMetrics(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "__type": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { @@ -21205,6 +21773,74 @@ func (ec *executionContext) marshalNCluster2ᚖgithubᚗcomᚋClusterCockpitᚋc return ec._Cluster(ctx, sel, v) } +func (ec *executionContext) marshalNClusterMetricWithName2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐClusterMetricWithNameᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.ClusterMetricWithName) 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.marshalNClusterMetricWithName2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐClusterMetricWithName(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) marshalNClusterMetricWithName2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐClusterMetricWithName(ctx context.Context, sel ast.SelectionSet, v *model.ClusterMetricWithName) 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._ClusterMetricWithName(ctx, sel, v) +} + +func (ec *executionContext) marshalNClusterMetrics2githubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐClusterMetrics(ctx context.Context, sel ast.SelectionSet, v model.ClusterMetrics) graphql.Marshaler { + return ec._ClusterMetrics(ctx, sel, &v) +} + +func (ec *executionContext) marshalNClusterMetrics2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐClusterMetrics(ctx context.Context, sel ast.SelectionSet, v *model.ClusterMetrics) 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._ClusterMetrics(ctx, sel, v) +} + func (ec *executionContext) marshalNClusterSupport2githubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐClusterSupport(ctx context.Context, sel ast.SelectionSet, v schema.ClusterSupport) graphql.Marshaler { return ec._ClusterSupport(ctx, sel, &v) } @@ -24142,6 +24778,13 @@ func (ec *executionContext) marshalOUnit2githubᚗcomᚋClusterCockpitᚋccᚑli return ec._Unit(ctx, sel, &v) } +func (ec *executionContext) marshalOUnit2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐUnit(ctx context.Context, sel ast.SelectionSet, v *schema.Unit) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Unit(ctx, sel, v) +} + func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v *model.User) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/internal/graph/model/models_gen.go b/internal/graph/model/models_gen.go index 4cb414e..63b2da5 100644 --- a/internal/graph/model/models_gen.go +++ b/internal/graph/model/models_gen.go @@ -13,6 +13,18 @@ import ( "github.com/ClusterCockpit/cc-lib/schema" ) +type ClusterMetricWithName struct { + Name string `json:"name"` + Unit *schema.Unit `json:"unit,omitempty"` + Timestep int `json:"timestep"` + Data []schema.Float `json:"data"` +} + +type ClusterMetrics struct { + NodeCount int `json:"nodeCount"` + Metrics []*ClusterMetricWithName `json:"metrics"` +} + type Count struct { Name string `json:"name"` Count int `json:"count"` diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index e4901c4..624ddc6 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "math" "regexp" "slices" "strconv" @@ -973,6 +974,86 @@ func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, sub return nodeMetricsListResult, nil } +// ClusterMetrics is the resolver for the clusterMetrics field. +func (r *queryResolver) ClusterMetrics(ctx context.Context, cluster string, metrics []string, from time.Time, to time.Time) (*model.ClusterMetrics, error) { + + user := repository.GetUserFromContext(ctx) + if user != nil && !user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport}) { + return nil, errors.New("you need to be administrator or support staff for this query") + } + + if metrics == nil { + for _, mc := range archive.GetCluster(cluster).MetricConfig { + metrics = append(metrics, mc.Name) + } + } + + // 'nodes' == nil -> Defaults to all nodes of cluster for existing query workflow + scopes := []schema.MetricScope{"node"} + data, err := metricDataDispatcher.LoadNodeData(cluster, metrics, nil, scopes, from, to, ctx) + if err != nil { + cclog.Warn("error while loading node data") + return nil, err + } + + clusterMetricData := make([]*model.ClusterMetricWithName, 0) + clusterMetrics := model.ClusterMetrics{NodeCount: 0, Metrics: clusterMetricData} + + collectorTimestep := make(map[string]int) + collectorUnit := make(map[string]schema.Unit) + collectorData := make(map[string][]schema.Float) + + for _, metrics := range data { + clusterMetrics.NodeCount += 1 + for metric, scopedMetrics := range metrics { + _, ok := collectorData[metric] + if !ok { + collectorData[metric] = make([]schema.Float, 0) + for _, scopedMetric := range scopedMetrics { + // Collect Info + collectorTimestep[metric] = scopedMetric.Timestep + collectorUnit[metric] = scopedMetric.Unit + // Collect Initial Data + for _, ser := range scopedMetric.Series { + for _, val := range ser.Data { + collectorData[metric] = append(collectorData[metric], val) + } + } + } + } else { + // Sum up values by index + for _, scopedMetric := range scopedMetrics { + // For This Purpose (Cluster_Wide-Sum of Node Metrics) OK + for _, ser := range scopedMetric.Series { + for i, val := range ser.Data { + collectorData[metric][i] += val + } + } + } + } + } + } + + for metricName, data := range collectorData { + cu := collectorUnit[metricName] + roundedData := make([]schema.Float, 0) + for _, val := range data { + roundedData = append(roundedData, schema.Float((math.Round(float64(val)*100.0) / 100.0))) + } + + cm := model.ClusterMetricWithName{ + Name: metricName, + Unit: &cu, + Timestep: collectorTimestep[metricName], + Data: roundedData, + } + + clusterMetrics.Metrics = append(clusterMetrics.Metrics, &cm) + } + + return &clusterMetrics, nil +} + // NumberOfNodes is the resolver for the numberOfNodes field. func (r *subClusterResolver) NumberOfNodes(ctx context.Context, obj *schema.SubCluster) (int, error) { nodeList, err := archive.ParseNodeList(obj.Nodes) diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index 9c19de5..71edeef 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -47,7 +47,9 @@ var routes []Route = []Route{ {"/monitoring/systems/list/{cluster}/{subcluster}", "monitoring/systems.tmpl", "Cluster Node List - ClusterCockpit", false, setupClusterListRoute}, {"/monitoring/node/{cluster}/{hostname}", "monitoring/node.tmpl", "Node - ClusterCockpit", false, setupNodeRoute}, {"/monitoring/analysis/{cluster}", "monitoring/analysis.tmpl", "Analysis - ClusterCockpit", true, setupAnalysisRoute}, - {"/monitoring/status/{cluster}", "monitoring/status.tmpl", "Status of - ClusterCockpit", false, setupClusterStatusRoute}, + {"/monitoring/status/{cluster}", "monitoring/status.tmpl", " Dashboard - ClusterCockpit", false, setupClusterStatusRoute}, + {"/monitoring/status/detail/{cluster}", "monitoring/status.tmpl", "Status of - ClusterCockpit", false, setupClusterDetailRoute}, + {"/monitoring/dashboard/{cluster}", "monitoring/dashboard.tmpl", " Dashboard - ClusterCockpit", false, setupDashboardRoute}, } func setupHomeRoute(i InfoType, r *http.Request) InfoType { @@ -117,6 +119,33 @@ func setupClusterStatusRoute(i InfoType, r *http.Request) InfoType { vars := mux.Vars(r) i["id"] = vars["cluster"] i["cluster"] = vars["cluster"] + i["displayType"] = "DASHBOARD" + from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to") + if from != "" || to != "" { + i["from"] = from + i["to"] = to + } + return i +} + +func setupClusterDetailRoute(i InfoType, r *http.Request) InfoType { + vars := mux.Vars(r) + i["id"] = vars["cluster"] + i["cluster"] = vars["cluster"] + i["displayType"] = "DETAILS" + from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to") + if from != "" || to != "" { + i["from"] = from + i["to"] = to + } + return i +} + +func setupDashboardRoute(i InfoType, r *http.Request) InfoType { + vars := mux.Vars(r) + i["id"] = vars["cluster"] + i["cluster"] = vars["cluster"] + i["displayType"] = "PUBLIC" from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to") if from != "" || to != "" { i["from"] = from diff --git a/web/frontend/rollup.config.mjs b/web/frontend/rollup.config.mjs index c92d815..6b7cf88 100644 --- a/web/frontend/rollup.config.mjs +++ b/web/frontend/rollup.config.mjs @@ -74,5 +74,6 @@ export default [ entrypoint('node', 'src/node.entrypoint.js'), entrypoint('analysis', 'src/analysis.entrypoint.js'), entrypoint('status', 'src/status.entrypoint.js'), + entrypoint('dashpublic', 'src/dashpublic.entrypoint.js'), entrypoint('config', 'src/config.entrypoint.js') ]; diff --git a/web/frontend/src/DashPublic.root.svelte b/web/frontend/src/DashPublic.root.svelte new file mode 100644 index 0000000..e344aa4 --- /dev/null +++ b/web/frontend/src/DashPublic.root.svelte @@ -0,0 +1,671 @@ + + + + + + +

{presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)} Dashboard

+
+ + {#if $statusQuery.fetching || $statesTimed.fetching || $topJobsQuery.fetching || $nodeStatusQuery.fetching} + + + + + + + {:else if $statusQuery.error || $statesTimed.error || $topJobsQuery.error || $nodeStatusQuery.error} + + {#if $statusQuery.error} + + Error Requesting StatusQuery: {$statusQuery.error.message} + + {/if} + {#if $statesTimed.error} + + Error Requesting StatesTimed: {$statesTimed.error.message} + + {/if} + {#if $topJobsQuery.error} + + Error Requesting TopJobsQuery: {$topJobsQuery.error.message} + + {/if} + {#if $nodeStatusQuery.error} + + Error Requesting NodeStatusQuery: {$nodeStatusQuery.error.message} + + {/if} + + + {:else} + + + + + Cluster "{presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)}" + {[...clusterInfo?.processorTypes].toString()} + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + {#if clusterInfo?.totalAccs !== 0} + + + + + + {/if} +
{clusterInfo?.runningJobs} Running Jobs{clusterInfo?.activeUsers} Active Users
+ Flop Rate (Any) + + Memory BW Rate +
+ {clusterInfo?.flopRate} + {clusterInfo?.flopRateUnit} + + {clusterInfo?.memBwRate} + {clusterInfo?.memBwRateUnit} +
Allocated Nodes
+ +
{clusterInfo?.allocatedNodes} / {clusterInfo?.totalNodes} + Nodes
Allocated Cores
+ +
{formatNumber(clusterInfo?.allocatedCores)} / {formatNumber(clusterInfo?.totalCores)} + Cores
Allocated Accelerators
+ +
{clusterInfo?.allocatedAccs} / {clusterInfo?.totalAccs} + Accelerators
+
+
+ + + + +
+ {#key refinedStateData} +

+ Current Node States +

+ sd.count, + )} + entities={refinedStateData.map( + (sd) => sd.state, + )} + /> + {/key} +
+ + + {#key refinedStateData} + + + + + + + {#each refinedStateData as sd, i} + + + + + + {/each} +
Current StateNodes
{sd.state}{sd.count}
+ {/key} + +
+ + + + + + Infos + + + Contents + + + + +
+ {#key $statusQuery?.data?.nodeMetrics} + + {/key} +
+ + +
+ +
+ + + +
+ {#key $statesTimed?.data?.nodeStates} + + {/key} +
+ +
+ {/if} +
+
diff --git a/web/frontend/src/Header.svelte b/web/frontend/src/Header.svelte index 98a796a..f7ceac2 100644 --- a/web/frontend/src/Header.svelte +++ b/web/frontend/src/Header.svelte @@ -120,7 +120,7 @@ href: "/monitoring/status/", icon: "clipboard-data", perCluster: true, - listOptions: false, + listOptions: true, menu: "Info", }, ]; diff --git a/web/frontend/src/Status.root.svelte b/web/frontend/src/Status.root.svelte index 3d9002a..8175681 100644 --- a/web/frontend/src/Status.root.svelte +++ b/web/frontend/src/Status.root.svelte @@ -6,77 +6,43 @@ --> - - - + - -{#if $initq.fetching} - +{#if displayType !== "DASHBOARD" && displayType !== "DETAILS"} + - - - -{:else if $initq.error} - - - {$initq.error.message} + Unknown displayList type! {:else} - - - - - - - - - - - - - - - - - - - - - + {#if displayStatusDetail} + + + {:else} + + + {/if} {/if} diff --git a/web/frontend/src/dashpublic.entrypoint.js b/web/frontend/src/dashpublic.entrypoint.js new file mode 100644 index 0000000..47287c7 --- /dev/null +++ b/web/frontend/src/dashpublic.entrypoint.js @@ -0,0 +1,13 @@ +import { mount } from 'svelte'; +// import {} from './header.entrypoint.js' +import DashPublic from './DashPublic.root.svelte' + +mount(DashPublic, { + target: document.getElementById('svelte-app'), + props: { + presetCluster: infos.cluster, + }, + context: new Map([ + ['cc-config', clusterCockpitConfig] + ]) +}) diff --git a/web/frontend/src/generic/plots/DoubleMetricPlot.svelte b/web/frontend/src/generic/plots/DoubleMetricPlot.svelte new file mode 100644 index 0000000..94acf45 --- /dev/null +++ b/web/frontend/src/generic/plots/DoubleMetricPlot.svelte @@ -0,0 +1,640 @@ + + + + + +{#if metricData[0]?.data && metricData[0]?.data?.length > 0} +
+{:else} + Cannot render plot: No series data returned for {cluster} +{/if} diff --git a/web/frontend/src/generic/plots/Roofline.svelte b/web/frontend/src/generic/plots/Roofline.svelte index 79ece22..6425275 100644 --- a/web/frontend/src/generic/plots/Roofline.svelte +++ b/web/frontend/src/generic/plots/Roofline.svelte @@ -36,6 +36,7 @@ subCluster = null, allowSizeChange = false, useColors = true, + useLegend = true, width = 600, height = 380, } = $props(); @@ -534,7 +535,7 @@ width: width, height: height, legend: { - show: true, + show: useLegend, }, cursor: { dataIdx: (u, seriesIdx) => { diff --git a/web/frontend/src/generic/plots/Stacked.svelte b/web/frontend/src/generic/plots/Stacked.svelte index 4c532db..2616f9a 100644 --- a/web/frontend/src/generic/plots/Stacked.svelte +++ b/web/frontend/src/generic/plots/Stacked.svelte @@ -156,7 +156,7 @@ { scale: "y", grid: { show: true }, - labelFont: "sans-serif", + // labelFont: "sans-serif", label: ylabel + (yunit ? ` (${yunit})` : ''), // values: (u, vals) => vals.map((v) => formatNumber(v)), }, diff --git a/web/frontend/src/header/NavbarLinks.svelte b/web/frontend/src/header/NavbarLinks.svelte index 41454d1..04fec0e 100644 --- a/web/frontend/src/header/NavbarLinks.svelte +++ b/web/frontend/src/header/NavbarLinks.svelte @@ -64,6 +64,34 @@ {/each} + {:else if item.title === 'Status'} + + + + {item.title} + + + {#each clusters as cluster} + + + {cluster.name} + + + + Status Dashboard + + + Status Details + + + + {/each} + + {:else} diff --git a/web/frontend/src/status.entrypoint.js b/web/frontend/src/status.entrypoint.js index c3407c1..e21b361 100644 --- a/web/frontend/src/status.entrypoint.js +++ b/web/frontend/src/status.entrypoint.js @@ -6,6 +6,7 @@ mount(Status, { target: document.getElementById('svelte-app'), props: { presetCluster: infos.cluster, + displayType: displayType, }, context: new Map([ ['cc-config', clusterCockpitConfig] diff --git a/web/frontend/src/status/DashDetails.svelte b/web/frontend/src/status/DashDetails.svelte new file mode 100644 index 0000000..410d8df --- /dev/null +++ b/web/frontend/src/status/DashDetails.svelte @@ -0,0 +1,82 @@ + + + + + + + + +

Current Status of Cluster "{presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)}"

+ +
+ + +{#if $initq.fetching} + + + + + +{:else if $initq.error} + + + {$initq.error.message} + + +{:else} + + + + + + + + + + + + + + + + + + + + + +{/if} diff --git a/web/frontend/src/status/DashInternal.svelte b/web/frontend/src/status/DashInternal.svelte new file mode 100644 index 0000000..73fa639 --- /dev/null +++ b/web/frontend/src/status/DashInternal.svelte @@ -0,0 +1,605 @@ + + + + + + +

{presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)} Dashboard

+
+ + {#if $statusQuery.fetching || $statesTimed.fetching || $topJobsQuery.fetching || $nodeStatusQuery.fetching} + + + + + + + {:else if $statusQuery.error || $statesTimed.error || $topJobsQuery.error || $nodeStatusQuery.error} + + {#if $statusQuery.error} + + Error Requesting StatusQuery: {$statusQuery.error.message} + + {/if} + {#if $statesTimed.error} + + Error Requesting StatesTimed: {$statesTimed.error.message} + + {/if} + {#if $topJobsQuery.error} + + Error Requesting TopJobsQuery: {$topJobsQuery.error.message} + + {/if} + {#if $nodeStatusQuery.error} + + Error Requesting NodeStatusQuery: {$nodeStatusQuery.error.message} + + {/if} + + + {:else} + + + + + Cluster "{presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)}" + {[...clusterInfo?.processorTypes].toString()} + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + {#if clusterInfo?.totalAccs !== 0} + + + + + + {/if} +
{clusterInfo?.runningJobs} Running Jobs{clusterInfo?.activeUsers} Active Users
+ Flop Rate (Any) + + Memory BW Rate +
+ {clusterInfo?.flopRate} + {clusterInfo?.flopRateUnit} + + {clusterInfo?.memBwRate} + {clusterInfo?.memBwRateUnit} +
Allocated Nodes
+ +
{clusterInfo?.allocatedNodes} / {clusterInfo?.totalNodes} + Nodes
Allocated Cores
+ +
{formatNumber(clusterInfo?.allocatedCores)} / {formatNumber(clusterInfo?.totalCores)} + Cores
Allocated Accelerators
+ +
{clusterInfo?.allocatedAccs} / {clusterInfo?.totalAccs} + Accelerators
+
+
+ + + + +
+

+ Top Projects: Jobs +

+ tp['totalJobs'], + )} + entities={$topJobsQuery.data.jobsStatistics.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)} + /> +
+ + + + + + + + + {#each $topJobsQuery.data.jobsStatistics as tp, i} + + + + + + {/each} +
ProjectJobs
+ {scrambleNames ? scramble(tp.id) : tp.id} + + {tp['totalJobs']}
+ +
+ + +
+ {#key $statusQuery?.data?.jobsMetricStats} + + {/key} +
+ + + {#if clusterInfo?.totalAccs == 0} + + {:else} + + {/if} + + +
+ {#key $statesTimed?.data?.nodeStates} + + {/key} +
+ + +
+ {#key $statesTimed?.data?.healthStates} + + {/key} +
+ +
+ {/if} +
+
diff --git a/web/frontend/src/status/StatisticsDash.svelte b/web/frontend/src/status/dashdetails/StatisticsDash.svelte similarity index 92% rename from web/frontend/src/status/StatisticsDash.svelte rename to web/frontend/src/status/dashdetails/StatisticsDash.svelte index efd7a4c..0a1cd46 100644 --- a/web/frontend/src/status/StatisticsDash.svelte +++ b/web/frontend/src/status/dashdetails/StatisticsDash.svelte @@ -22,11 +22,11 @@ } from "@urql/svelte"; import { convert2uplot, - } from "../generic/utils.js"; - import PlotGrid from "../generic/PlotGrid.svelte"; - import Histogram from "../generic/plots/Histogram.svelte"; - import HistogramSelection from "../generic/select/HistogramSelection.svelte"; - import Refresher from "../generic/helper/Refresher.svelte"; + } from "../../generic/utils.js"; + import PlotGrid from "../../generic/PlotGrid.svelte"; + import Histogram from "../../generic/plots/Histogram.svelte"; + import HistogramSelection from "../../generic/select/HistogramSelection.svelte"; + import Refresher from "../../generic/helper/Refresher.svelte"; /* Svelte 5 Props */ let { diff --git a/web/frontend/src/status/StatusDash.svelte b/web/frontend/src/status/dashdetails/StatusDash.svelte similarity index 97% rename from web/frontend/src/status/StatusDash.svelte rename to web/frontend/src/status/dashdetails/StatusDash.svelte index 03a8cc4..ee60483 100644 --- a/web/frontend/src/status/StatusDash.svelte +++ b/web/frontend/src/status/dashdetails/StatusDash.svelte @@ -22,12 +22,12 @@ gql, getContextClient, } from "@urql/svelte"; - import { formatDurationTime } from "../generic/units.js"; - import Refresher from "../generic/helper/Refresher.svelte"; - import TimeSelection from "../generic/select/TimeSelection.svelte"; - import Roofline from "../generic/plots/Roofline.svelte"; - import Pie, { colors } from "../generic/plots/Pie.svelte"; - import Stacked from "../generic/plots/Stacked.svelte"; + import { formatDurationTime } from "../../generic/units.js"; + import Refresher from "../../generic/helper/Refresher.svelte"; + import TimeSelection from "../../generic/select/TimeSelection.svelte"; + import Roofline from "../../generic/plots/Roofline.svelte"; + import Pie, { colors } from "../../generic/plots/Pie.svelte"; + import Stacked from "../../generic/plots/Stacked.svelte"; /* Svelte 5 Props */ let { @@ -83,7 +83,7 @@ } `, variables: { - filter: { cluster: { eq: cluster }, timeStart: stackedFrom}, + filter: { cluster: { eq: cluster }, timeStart: 1760096999}, typeNode: "node", typeHealth: "health" }, diff --git a/web/frontend/src/status/UsageDash.svelte b/web/frontend/src/status/dashdetails/UsageDash.svelte similarity index 98% rename from web/frontend/src/status/UsageDash.svelte rename to web/frontend/src/status/dashdetails/UsageDash.svelte index 74dd7a9..2071465 100644 --- a/web/frontend/src/status/UsageDash.svelte +++ b/web/frontend/src/status/dashdetails/UsageDash.svelte @@ -27,10 +27,10 @@ scramble, scrambleNames, convert2uplot, - } from "../generic/utils.js"; - import Pie, { colors } from "../generic/plots/Pie.svelte"; - import Histogram from "../generic/plots/Histogram.svelte"; - import Refresher from "../generic/helper/Refresher.svelte"; + } from "../../generic/utils.js"; + import Pie, { colors } from "../../generic/plots/Pie.svelte"; + import Histogram from "../../generic/plots/Histogram.svelte"; + import Refresher from "../../generic/helper/Refresher.svelte"; /* Svelte 5 Props */ let { diff --git a/web/templates/base.tmpl b/web/templates/base.tmpl index 7fd35a0..28eab33 100644 --- a/web/templates/base.tmpl +++ b/web/templates/base.tmpl @@ -23,34 +23,49 @@ - {{block "navigation" .}} -
- {{end}} + {{if eq .Infos.displayType "PUBLIC"}} +
+
+ {{block "content-public" .}} + Whoops, you should not see this... [MAIN] + {{end}} +
+
-
-
- {{block "content" .}} - Whoops, you should not see this... - {{end}} -
-
+ {{block "javascript-public" .}} + Whoops, you should not see this... [JS] + {{end}} - {{block "footer" .}} -
- -
    -
  • Version {{ .Build.Version }}
  • -
  • Hash {{ .Build.Hash }}
  • -
  • Built {{ .Build.Buildtime }}
  • -
-
- {{end}} + {{else}} + {{block "navigation" .}} +
+ {{end}} - {{block "javascript" .}} - +
+
+ {{block "content" .}} + Whoops, you should not see this... [MAIN] + {{end}} +
+
+ + {{block "footer" .}} +
+ +
    +
  • Version {{ .Build.Version }}
  • +
  • Hash {{ .Build.Hash }}
  • +
  • Built {{ .Build.Buildtime }}
  • +
+
+ {{end}} + + {{block "javascript" .}} + + {{end}} {{end}} diff --git a/web/templates/monitoring/dashboard.tmpl b/web/templates/monitoring/dashboard.tmpl new file mode 100644 index 0000000..06666cd --- /dev/null +++ b/web/templates/monitoring/dashboard.tmpl @@ -0,0 +1,14 @@ +{{define "content-public"}} +
+{{end}} + +{{define "stylesheets"}} + +{{end}} +{{define "javascript-public"}} + + +{{end}} diff --git a/web/templates/monitoring/status.tmpl b/web/templates/monitoring/status.tmpl index 15aff69..b6a8414 100644 --- a/web/templates/monitoring/status.tmpl +++ b/web/templates/monitoring/status.tmpl @@ -8,6 +8,7 @@ {{define "javascript"}}