mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-03-21 07:17:30 +01:00
change: remove heuristic metricHealth, replace with DB metricHealth
- add metricHealth to single Node view
This commit is contained in:
@@ -270,7 +270,8 @@ enum SortByAggregate {
|
|||||||
|
|
||||||
type NodeMetrics {
|
type NodeMetrics {
|
||||||
host: String!
|
host: String!
|
||||||
state: String!
|
nodeState: String!
|
||||||
|
metricHealth: String!
|
||||||
subCluster: String!
|
subCluster: String!
|
||||||
metrics: [JobMetricWithName!]!
|
metrics: [JobMetricWithName!]!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,10 +288,11 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NodeMetrics struct {
|
NodeMetrics struct {
|
||||||
Host func(childComplexity int) int
|
Host func(childComplexity int) int
|
||||||
Metrics func(childComplexity int) int
|
MetricHealth func(childComplexity int) int
|
||||||
State func(childComplexity int) int
|
Metrics func(childComplexity int) int
|
||||||
SubCluster func(childComplexity int) int
|
NodeState func(childComplexity int) int
|
||||||
|
SubCluster func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeStateResultList struct {
|
NodeStateResultList struct {
|
||||||
@@ -1501,18 +1502,24 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return e.ComplexityRoot.NodeMetrics.Host(childComplexity), true
|
return e.ComplexityRoot.NodeMetrics.Host(childComplexity), true
|
||||||
|
case "NodeMetrics.metricHealth":
|
||||||
|
if e.ComplexityRoot.NodeMetrics.MetricHealth == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.ComplexityRoot.NodeMetrics.MetricHealth(childComplexity), true
|
||||||
case "NodeMetrics.metrics":
|
case "NodeMetrics.metrics":
|
||||||
if e.ComplexityRoot.NodeMetrics.Metrics == nil {
|
if e.ComplexityRoot.NodeMetrics.Metrics == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.ComplexityRoot.NodeMetrics.Metrics(childComplexity), true
|
return e.ComplexityRoot.NodeMetrics.Metrics(childComplexity), true
|
||||||
case "NodeMetrics.state":
|
case "NodeMetrics.nodeState":
|
||||||
if e.ComplexityRoot.NodeMetrics.State == nil {
|
if e.ComplexityRoot.NodeMetrics.NodeState == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.ComplexityRoot.NodeMetrics.State(childComplexity), true
|
return e.ComplexityRoot.NodeMetrics.NodeState(childComplexity), true
|
||||||
case "NodeMetrics.subCluster":
|
case "NodeMetrics.subCluster":
|
||||||
if e.ComplexityRoot.NodeMetrics.SubCluster == nil {
|
if e.ComplexityRoot.NodeMetrics.SubCluster == nil {
|
||||||
break
|
break
|
||||||
@@ -2537,7 +2544,8 @@ enum SortByAggregate {
|
|||||||
|
|
||||||
type NodeMetrics {
|
type NodeMetrics {
|
||||||
host: String!
|
host: String!
|
||||||
state: String!
|
nodeState: String!
|
||||||
|
metricHealth: String!
|
||||||
subCluster: String!
|
subCluster: String!
|
||||||
metrics: [JobMetricWithName!]!
|
metrics: [JobMetricWithName!]!
|
||||||
}
|
}
|
||||||
@@ -8316,14 +8324,14 @@ func (ec *executionContext) fieldContext_NodeMetrics_host(_ context.Context, fie
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _NodeMetrics_state(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) {
|
func (ec *executionContext) _NodeMetrics_nodeState(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
ec.OperationContext,
|
ec.OperationContext,
|
||||||
field,
|
field,
|
||||||
ec.fieldContext_NodeMetrics_state,
|
ec.fieldContext_NodeMetrics_nodeState,
|
||||||
func(ctx context.Context) (any, error) {
|
func(ctx context.Context) (any, error) {
|
||||||
return obj.State, nil
|
return obj.NodeState, nil
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
ec.marshalNString2string,
|
ec.marshalNString2string,
|
||||||
@@ -8332,7 +8340,36 @@ func (ec *executionContext) _NodeMetrics_state(ctx context.Context, field graphq
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_NodeMetrics_state(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_NodeMetrics_nodeState(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "NodeMetrics",
|
||||||
|
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) _NodeMetrics_metricHealth(ctx context.Context, field graphql.CollectedField, obj *model.NodeMetrics) (ret graphql.Marshaler) {
|
||||||
|
return graphql.ResolveField(
|
||||||
|
ctx,
|
||||||
|
ec.OperationContext,
|
||||||
|
field,
|
||||||
|
ec.fieldContext_NodeMetrics_metricHealth,
|
||||||
|
func(ctx context.Context) (any, error) {
|
||||||
|
return obj.MetricHealth, nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
ec.marshalNString2string,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_NodeMetrics_metricHealth(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
fc = &graphql.FieldContext{
|
fc = &graphql.FieldContext{
|
||||||
Object: "NodeMetrics",
|
Object: "NodeMetrics",
|
||||||
Field: field,
|
Field: field,
|
||||||
@@ -8666,8 +8703,10 @@ func (ec *executionContext) fieldContext_NodesResultList_items(_ context.Context
|
|||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "host":
|
case "host":
|
||||||
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
||||||
case "state":
|
case "nodeState":
|
||||||
return ec.fieldContext_NodeMetrics_state(ctx, field)
|
return ec.fieldContext_NodeMetrics_nodeState(ctx, field)
|
||||||
|
case "metricHealth":
|
||||||
|
return ec.fieldContext_NodeMetrics_metricHealth(ctx, field)
|
||||||
case "subCluster":
|
case "subCluster":
|
||||||
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
||||||
case "metrics":
|
case "metrics":
|
||||||
@@ -9844,8 +9883,10 @@ func (ec *executionContext) fieldContext_Query_nodeMetrics(ctx context.Context,
|
|||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "host":
|
case "host":
|
||||||
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
return ec.fieldContext_NodeMetrics_host(ctx, field)
|
||||||
case "state":
|
case "nodeState":
|
||||||
return ec.fieldContext_NodeMetrics_state(ctx, field)
|
return ec.fieldContext_NodeMetrics_nodeState(ctx, field)
|
||||||
|
case "metricHealth":
|
||||||
|
return ec.fieldContext_NodeMetrics_metricHealth(ctx, field)
|
||||||
case "subCluster":
|
case "subCluster":
|
||||||
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
return ec.fieldContext_NodeMetrics_subCluster(ctx, field)
|
||||||
case "metrics":
|
case "metrics":
|
||||||
@@ -15917,8 +15958,13 @@ func (ec *executionContext) _NodeMetrics(ctx context.Context, sel ast.SelectionS
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
case "state":
|
case "nodeState":
|
||||||
out.Values[i] = ec._NodeMetrics_state(ctx, field, obj)
|
out.Values[i] = ec._NodeMetrics_nodeState(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
out.Invalids++
|
||||||
|
}
|
||||||
|
case "metricHealth":
|
||||||
|
out.Values[i] = ec._NodeMetrics_metricHealth(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
out.Invalids++
|
out.Invalids++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,10 +193,11 @@ type NodeFilter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NodeMetrics struct {
|
type NodeMetrics struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
State string `json:"state"`
|
NodeState string `json:"nodeState"`
|
||||||
SubCluster string `json:"subCluster"`
|
MetricHealth string `json:"metricHealth"`
|
||||||
Metrics []*JobMetricWithName `json:"metrics"`
|
SubCluster string `json:"subCluster"`
|
||||||
|
Metrics []*JobMetricWithName `json:"metrics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeStateResultList struct {
|
type NodeStateResultList struct {
|
||||||
|
|||||||
@@ -840,14 +840,15 @@ func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, nodes [
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodeRepo := repository.GetNodeRepository()
|
nodeRepo := repository.GetNodeRepository()
|
||||||
stateMap, _ := nodeRepo.MapNodes(cluster)
|
nodeStateMap, metricHealthMap, _ := nodeRepo.MapNodes(cluster)
|
||||||
|
|
||||||
nodeMetrics := make([]*model.NodeMetrics, 0, len(data))
|
nodeMetrics := make([]*model.NodeMetrics, 0, len(data))
|
||||||
for hostname, metrics := range data {
|
for hostname, metrics := range data {
|
||||||
host := &model.NodeMetrics{
|
host := &model.NodeMetrics{
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
State: stateMap[hostname],
|
NodeState: nodeStateMap[hostname],
|
||||||
Metrics: make([]*model.JobMetricWithName, 0, len(metrics)*len(scopes)),
|
MetricHealth: metricHealthMap[hostname],
|
||||||
|
Metrics: make([]*model.JobMetricWithName, 0, len(metrics)*len(scopes)),
|
||||||
}
|
}
|
||||||
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -889,7 +890,7 @@ func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, sub
|
|||||||
|
|
||||||
nodeRepo := repository.GetNodeRepository()
|
nodeRepo := repository.GetNodeRepository()
|
||||||
// nodes -> array hostname
|
// nodes -> array hostname
|
||||||
nodes, stateMap, countNodes, hasNextPage, nerr := nodeRepo.GetNodesForList(ctx, cluster, subCluster, stateFilter, nodeFilter, page)
|
nodes, nodeStateMap, metricHealthMap, countNodes, hasNextPage, nerr := nodeRepo.GetNodesForList(ctx, cluster, subCluster, stateFilter, nodeFilter, page)
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
return nil, errors.New("could not retrieve node list required for resolving NodeMetricsList")
|
return nil, errors.New("could not retrieve node list required for resolving NodeMetricsList")
|
||||||
}
|
}
|
||||||
@@ -910,9 +911,10 @@ func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, sub
|
|||||||
nodeMetricsList := make([]*model.NodeMetrics, 0, len(data))
|
nodeMetricsList := make([]*model.NodeMetrics, 0, len(data))
|
||||||
for _, hostname := range nodes {
|
for _, hostname := range nodes {
|
||||||
host := &model.NodeMetrics{
|
host := &model.NodeMetrics{
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
State: stateMap[hostname],
|
NodeState: nodeStateMap[hostname],
|
||||||
Metrics: make([]*model.JobMetricWithName, 0),
|
MetricHealth: metricHealthMap[hostname],
|
||||||
|
Metrics: make([]*model.JobMetricWithName, 0),
|
||||||
}
|
}
|
||||||
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
host.SubCluster, err = archive.GetSubClusterByNode(cluster, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -593,8 +593,8 @@ func (r *NodeRepository) ListNodes(cluster string) ([]*schema.Node, error) {
|
|||||||
return nodeList, nil
|
return nodeList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NodeRepository) MapNodes(cluster string) (map[string]string, error) {
|
func (r *NodeRepository) MapNodes(cluster string) (map[string]string, map[string]string, error) {
|
||||||
q := sq.Select("node.hostname", "node_state.node_state").
|
q := sq.Select("node.hostname", "node_state.node_state", "node_state.health_state").
|
||||||
From("node").
|
From("node").
|
||||||
Join("node_state ON node_state.node_id = node.id").
|
Join("node_state ON node_state.node_id = node.id").
|
||||||
Where(latestStateCondition()).
|
Where(latestStateCondition()).
|
||||||
@@ -604,22 +604,25 @@ func (r *NodeRepository) MapNodes(cluster string) (map[string]string, error) {
|
|||||||
rows, err := q.RunWith(r.DB).Query()
|
rows, err := q.RunWith(r.DB).Query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Warn("Error while querying node list")
|
cclog.Warn("Error while querying node list")
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMap := make(map[string]string)
|
nodeStateMap := make(map[string]string)
|
||||||
|
metricHealthMap := make(map[string]string)
|
||||||
|
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var hostname, nodestate string
|
var hostname, nodeState, metricHealth string
|
||||||
if err := rows.Scan(&hostname, &nodestate); err != nil {
|
if err := rows.Scan(&hostname, &nodeState, &metricHealth); err != nil {
|
||||||
cclog.Warn("Error while scanning node list (MapNodes)")
|
cclog.Warn("Error while scanning node list (MapNodes)")
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMap[hostname] = nodestate
|
nodeStateMap[hostname] = nodeState
|
||||||
|
metricHealthMap[hostname] = metricHealth
|
||||||
}
|
}
|
||||||
|
|
||||||
return stateMap, nil
|
return nodeStateMap, metricHealthMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NodeRepository) CountStates(ctx context.Context, filters []*model.NodeFilter, column string) ([]*model.NodeStates, error) {
|
func (r *NodeRepository) CountStates(ctx context.Context, filters []*model.NodeFilter, column string) ([]*model.NodeStates, error) {
|
||||||
@@ -741,10 +744,11 @@ func (r *NodeRepository) GetNodesForList(
|
|||||||
stateFilter string,
|
stateFilter string,
|
||||||
nodeFilter string,
|
nodeFilter string,
|
||||||
page *model.PageRequest,
|
page *model.PageRequest,
|
||||||
) ([]string, map[string]string, int, bool, error) {
|
) ([]string, map[string]string, map[string]string, int, bool, error) {
|
||||||
// Init Return Vars
|
// Init Return Vars
|
||||||
nodes := make([]string, 0)
|
nodes := make([]string, 0)
|
||||||
stateMap := make(map[string]string)
|
nodeStateMap := make(map[string]string)
|
||||||
|
metricHealthMap := make(map[string]string)
|
||||||
countNodes := 0
|
countNodes := 0
|
||||||
hasNextPage := false
|
hasNextPage := false
|
||||||
|
|
||||||
@@ -778,7 +782,7 @@ func (r *NodeRepository) GetNodesForList(
|
|||||||
rawNodes, serr := r.QueryNodes(ctx, queryFilters, page, nil) // Order not Used
|
rawNodes, serr := r.QueryNodes(ctx, queryFilters, page, nil) // Order not Used
|
||||||
if serr != nil {
|
if serr != nil {
|
||||||
cclog.Warn("error while loading node database data (Resolver.NodeMetricsList)")
|
cclog.Warn("error while loading node database data (Resolver.NodeMetricsList)")
|
||||||
return nil, nil, 0, false, serr
|
return nil, nil, nil, 0, false, serr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intermediate Node Result Info
|
// Intermediate Node Result Info
|
||||||
@@ -787,7 +791,8 @@ func (r *NodeRepository) GetNodesForList(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nodes = append(nodes, node.Hostname)
|
nodes = append(nodes, node.Hostname)
|
||||||
stateMap[node.Hostname] = string(node.NodeState)
|
nodeStateMap[node.Hostname] = string(node.NodeState)
|
||||||
|
metricHealthMap[node.Hostname] = string(node.HealthState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special Case: Find Nodes not in DB node table but in metricStore only
|
// Special Case: Find Nodes not in DB node table but in metricStore only
|
||||||
@@ -847,7 +852,7 @@ func (r *NodeRepository) GetNodesForList(
|
|||||||
countNodes, cerr = r.CountNodes(ctx, queryFilters)
|
countNodes, cerr = r.CountNodes(ctx, queryFilters)
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
cclog.Warn("error while counting node database data (Resolver.NodeMetricsList)")
|
cclog.Warn("error while counting node database data (Resolver.NodeMetricsList)")
|
||||||
return nil, nil, 0, false, cerr
|
return nil, nil, nil, 0, false, cerr
|
||||||
}
|
}
|
||||||
hasNextPage = page.Page*page.ItemsPerPage < countNodes
|
hasNextPage = page.Page*page.ItemsPerPage < countNodes
|
||||||
}
|
}
|
||||||
@@ -857,7 +862,7 @@ func (r *NodeRepository) GetNodesForList(
|
|||||||
nodes, countNodes, hasNextPage = getNodesFromTopol(cluster, subCluster, nodeFilter, page)
|
nodes, countNodes, hasNextPage = getNodesFromTopol(cluster, subCluster, nodeFilter, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes, stateMap, countNodes, hasNextPage, nil
|
return nodes, nodeStateMap, metricHealthMap, countNodes, hasNextPage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AccessCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilder, error) {
|
func AccessCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilder, error) {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@
|
|||||||
name
|
name
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
# Get Current States fir Pie Charts
|
# Get Current States for Pie Charts
|
||||||
nodeStates(filter: $nodeFilter) {
|
nodeStates(filter: $nodeFilter) {
|
||||||
state
|
state
|
||||||
count
|
count
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
|
query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
|
||||||
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
|
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
|
||||||
host
|
host
|
||||||
state
|
nodeState
|
||||||
|
metricHealth
|
||||||
subCluster
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name
|
name
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
// Node State Colors
|
// Node/Metric State Colors
|
||||||
const stateColors = {
|
const stateColors = {
|
||||||
allocated: 'success',
|
allocated: 'success',
|
||||||
reserved: 'info',
|
reserved: 'info',
|
||||||
@@ -100,7 +101,10 @@
|
|||||||
mixed: 'warning',
|
mixed: 'warning',
|
||||||
down: 'danger',
|
down: 'danger',
|
||||||
unknown: 'dark',
|
unknown: 'dark',
|
||||||
notindb: 'secondary'
|
notindb: 'secondary',
|
||||||
|
full: 'success',
|
||||||
|
partial: 'warning',
|
||||||
|
failed: 'danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
@@ -153,31 +157,46 @@
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const thisNodeState = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.state ? $nodeMetricsData.data.nodeMetrics[0].state : 'notindb');
|
const thisNodeState = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.nodeState || 'notindb');
|
||||||
|
const thisMetricHealth = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.metricHealth || 'unknown');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row cols={{ xs: 2, lg: 5 }}>
|
<Row cols={{ xs: 2, lg: 3}}>
|
||||||
{#if $initq.error}
|
{#if $initq.error}
|
||||||
<Card body color="danger">{$initq.error.message}</Card>
|
<Card body color="danger">{$initq.error.message}</Card>
|
||||||
{:else if $initq.fetching}
|
{:else if $initq.fetching}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Node Col -->
|
<!-- Node Col -->
|
||||||
<Col>
|
<Col class="mb-2">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupText><Icon name="hdd" /></InputGroupText>
|
<InputGroupText><Icon name="hdd" /></InputGroupText>
|
||||||
<InputGroupText>Selected Node</InputGroupText>
|
<InputGroupText>Selected Node</InputGroupText>
|
||||||
<Input style="background-color: white;" type="text" value="{hostname} [{cluster} {$nodeMetricsData?.data?.nodeMetrics[0] ? `(${$nodeMetricsData.data.nodeMetrics[0].subCluster})` : ''}]" disabled/>
|
<Input style="background-color: white;" type="text" value="{hostname} [{cluster} {$nodeMetricsData?.data?.nodeMetrics[0] ? `(${$nodeMetricsData.data.nodeMetrics[0].subCluster})` : ''}]" disabled/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
<!-- State Col -->
|
<!-- Node State Col -->
|
||||||
<Col>
|
<Col class="mb-2">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
||||||
<InputGroupText>Node State</InputGroupText>
|
<InputGroupText>Node State</InputGroupText>
|
||||||
<Button class="flex-grow-1 text-center" color={stateColors[thisNodeState]} disabled>
|
<Button class="flex-grow-1 text-center" color={stateColors[thisNodeState]} disabled>
|
||||||
{#if $nodeMetricsData?.data}
|
{#if $nodeMetricsData?.data}
|
||||||
{thisNodeState}
|
{thisNodeState.charAt(0).toUpperCase() + thisNodeState.slice(1)}
|
||||||
|
{:else}
|
||||||
|
<span><Spinner size="sm" secondary/></span>
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
|
</Col>
|
||||||
|
<!-- Metric Health Col -->
|
||||||
|
<Col class="mb-2">
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
||||||
|
<InputGroupText>Metric Health</InputGroupText>
|
||||||
|
<Button class="flex-grow-1 text-center" color={stateColors[thisMetricHealth]} disabled>
|
||||||
|
{#if $nodeMetricsData?.data}
|
||||||
|
{thisMetricHealth.charAt(0).toUpperCase() + thisMetricHealth.slice(1)}
|
||||||
{:else}
|
{:else}
|
||||||
<span><Spinner size="sm" secondary/></span>
|
<span><Spinner size="sm" secondary/></span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -185,7 +204,7 @@
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
<!-- Concurrent Col -->
|
<!-- Concurrent Col -->
|
||||||
<Col class="mt-2 mt-lg-0">
|
<Col>
|
||||||
{#if $nodeJobsData.fetching}
|
{#if $nodeJobsData.fetching}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else if $nodeJobsData.data}
|
{:else if $nodeJobsData.data}
|
||||||
@@ -217,7 +236,7 @@
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<!-- Refresh Col-->
|
<!-- Refresh Col-->
|
||||||
<Col class="mt-2 mt-lg-0">
|
<Col>
|
||||||
<Refresher
|
<Refresher
|
||||||
onRefresh={() => {
|
onRefresh={() => {
|
||||||
const diff = Date.now() - to;
|
const diff = Date.now() - to;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let hostnameFilter = $state("");
|
let hostnameFilter = $state("");
|
||||||
let hoststateFilter = $state("all");
|
let nodeStateFilter = $state("all");
|
||||||
let pendingHostnameFilter = $state("");
|
let pendingHostnameFilter = $state("");
|
||||||
let isMetricsSelectionOpen = $state(false);
|
let isMetricsSelectionOpen = $state(false);
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@
|
|||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
||||||
<InputGroupText>State</InputGroupText>
|
<InputGroupText>State</InputGroupText>
|
||||||
<Input type="select" bind:value={hoststateFilter}>
|
<Input type="select" bind:value={nodeStateFilter}>
|
||||||
{#each stateOptions as so}
|
{#each stateOptions as so}
|
||||||
<option value={so}>{so.charAt(0).toUpperCase() + so.slice(1)}</option>
|
<option value={so}>{so.charAt(0).toUpperCase() + so.slice(1)}</option>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -269,11 +269,11 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#if displayNodeOverview}
|
{#if displayNodeOverview}
|
||||||
<!-- ROW2-1: Node Overview (Grid Included)-->
|
<!-- ROW2-1: Node Overview (Grid Included)-->
|
||||||
<NodeOverview {cluster} {ccconfig} {selectedMetric} {globalMetrics} {from} {to} {hostnameFilter} {hoststateFilter}/>
|
<NodeOverview {cluster} {ccconfig} {selectedMetric} {globalMetrics} {from} {to} {hostnameFilter} {nodeStateFilter}/>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- ROW2-2: Node List (Grid Included)-->
|
<!-- ROW2-2: Node List (Grid Included)-->
|
||||||
<NodeList pendingSelectedMetrics={selectedMetrics} {cluster} {subCluster}
|
<NodeList pendingSelectedMetrics={selectedMetrics} {cluster} {subCluster}
|
||||||
{selectedResolution} {hostnameFilter} {hoststateFilter} {from} {to} {systemUnits}/>
|
{selectedResolution} {hostnameFilter} {nodeStateFilter} {from} {to} {systemUnits}/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
- `subCluster String`: The nodes' subCluster [Default: ""]
|
- `subCluster String`: The nodes' subCluster [Default: ""]
|
||||||
- `pendingSelectedMetrics [String]`: The array of selected metrics [Default []]
|
- `pendingSelectedMetrics [String]`: The array of selected metrics [Default []]
|
||||||
- `selectedResolution Number?`: The selected data resolution [Default: 0]
|
- `selectedResolution Number?`: The selected data resolution [Default: 0]
|
||||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
- `hostnameFilter String?`: The active hostname filter [Default: ""]
|
||||||
- `hoststateFilter String?`: The active hoststatefilter [Default: ""]
|
- `nodeStateFilter String?`: The active nodeState filter [Default: ""]
|
||||||
- `systemUnits Object`: The object of metric units [Default: null]
|
- `systemUnits Object`: The object of metric units [Default: null]
|
||||||
- `from Date?`: The selected "from" date [Default: null]
|
- `from Date?`: The selected "from" date [Default: null]
|
||||||
- `to Date?`: The selected "to" date [Default: null]
|
- `to Date?`: The selected "to" date [Default: null]
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
pendingSelectedMetrics = [],
|
pendingSelectedMetrics = [],
|
||||||
selectedResolution = 0,
|
selectedResolution = 0,
|
||||||
hostnameFilter = "",
|
hostnameFilter = "",
|
||||||
hoststateFilter = "",
|
nodeStateFilter = "",
|
||||||
systemUnits = null,
|
systemUnits = null,
|
||||||
from = null,
|
from = null,
|
||||||
to = null
|
to = null
|
||||||
@@ -54,7 +54,8 @@
|
|||||||
) {
|
) {
|
||||||
items {
|
items {
|
||||||
host
|
host
|
||||||
state
|
nodeState
|
||||||
|
metricHealth
|
||||||
subCluster
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name
|
name
|
||||||
@@ -110,7 +111,7 @@
|
|||||||
variables: {
|
variables: {
|
||||||
cluster: cluster,
|
cluster: cluster,
|
||||||
subCluster: subCluster,
|
subCluster: subCluster,
|
||||||
stateFilter: hoststateFilter,
|
stateFilter: nodeStateFilter,
|
||||||
nodeFilter: hostnameFilter,
|
nodeFilter: hostnameFilter,
|
||||||
scopes: ["core", "socket", "accelerator"],
|
scopes: ["core", "socket", "accelerator"],
|
||||||
metrics: pendingSelectedMetrics,
|
metrics: pendingSelectedMetrics,
|
||||||
@@ -164,7 +165,7 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Update NodeListRows metrics only: Keep ordered nodes on page 1
|
// Update NodeListRows metrics only: Keep ordered nodes on page 1
|
||||||
hostnameFilter, hoststateFilter
|
hostnameFilter, nodeStateFilter
|
||||||
// Continous Scroll: Paging if parameters change: Existing entries will not match new selections
|
// Continous Scroll: Paging if parameters change: Existing entries will not match new selections
|
||||||
nodes = [];
|
nodes = [];
|
||||||
if (!usePaging) {
|
if (!usePaging) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
||||||
- `cluster String`: The cluster to show status information for
|
- `cluster String`: The cluster to show status information for
|
||||||
- `selectedMetric String?`: The selectedMetric input [Default: ""]
|
- `selectedMetric String?`: The selectedMetric input [Default: ""]
|
||||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
- `hostnameFilter String?`: The active hostname filter [Default: ""]
|
||||||
- `hostnameFilter String?`: The active hoststatefilter [Default: ""]
|
- `nodeStateFilter String?`: The active nodeState filter [Default: ""]
|
||||||
- `from Date?`: The selected "from" date [Default: null]
|
- `from Date?`: The selected "from" date [Default: null]
|
||||||
- `to Date?`: The selected "to" date [Default: null]
|
- `to Date?`: The selected "to" date [Default: null]
|
||||||
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
|
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
cluster = "",
|
cluster = "",
|
||||||
selectedMetric = "",
|
selectedMetric = "",
|
||||||
hostnameFilter = "",
|
hostnameFilter = "",
|
||||||
hoststateFilter = "",
|
nodeStateFilter = "",
|
||||||
from = null,
|
from = null,
|
||||||
to = null,
|
to = null,
|
||||||
globalMetrics
|
globalMetrics
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
to: $to
|
to: $to
|
||||||
) {
|
) {
|
||||||
host
|
host
|
||||||
state
|
nodeState
|
||||||
subCluster
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name
|
name
|
||||||
@@ -91,11 +91,11 @@
|
|||||||
const mappedData = $derived(handleQueryData($nodesQuery?.data));
|
const mappedData = $derived(handleQueryData($nodesQuery?.data));
|
||||||
const filteredData = $derived(mappedData.filter((h) => {
|
const filteredData = $derived(mappedData.filter((h) => {
|
||||||
if (hostnameFilter) {
|
if (hostnameFilter) {
|
||||||
if (hoststateFilter == 'all') return h.host.includes(hostnameFilter)
|
if (nodeStateFilter == 'all') return h.host.includes(hostnameFilter)
|
||||||
else return (h.host.includes(hostnameFilter) && h.state == hoststateFilter)
|
else return (h.host.includes(hostnameFilter) && h.nodeState == nodeStateFilter)
|
||||||
} else {
|
} else {
|
||||||
if (hoststateFilter == 'all') return true
|
if (nodeStateFilter == 'all') return true
|
||||||
else return h.state == hoststateFilter
|
else return h.nodeState == nodeStateFilter
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
if (rawData.length > 0) {
|
if (rawData.length > 0) {
|
||||||
pendingMapped = rawData.map((h) => ({
|
pendingMapped = rawData.map((h) => ({
|
||||||
host: h.host,
|
host: h.host,
|
||||||
state: h?.state? h.state : 'notindb',
|
nodeState: h?.nodeState || 'notindb',
|
||||||
subCluster: h.subCluster,
|
subCluster: h.subCluster,
|
||||||
data: h.metrics.filter(
|
data: h.metrics.filter(
|
||||||
(m) => m?.name == selectedMetric && m.scope == "node",
|
(m) => m?.name == selectedMetric && m.scope == "node",
|
||||||
@@ -157,8 +157,8 @@
|
|||||||
>
|
>
|
||||||
</h4>
|
</h4>
|
||||||
<span style="margin-right: 0.5rem;">
|
<span style="margin-right: 0.5rem;">
|
||||||
<Badge color={stateColors[item?.state? item.state : 'notindb']}>
|
<Badge color={stateColors[item?.nodeState || 'notindb']}>
|
||||||
State: {item?.state? item.state.charAt(0).toUpperCase() + item.state.slice(1) : 'Not in DB'}
|
State: {item?.nodeState ? item.nodeState.charAt(0).toUpperCase() + item.nodeState.slice(1) : 'Not in DB'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/key}
|
{/key}
|
||||||
</Row>
|
</Row>
|
||||||
{:else if hostnameFilter || hoststateFilter != 'all'}
|
{:else if hostnameFilter || nodeStateFilter != 'all'}
|
||||||
<Row class="mx-1">
|
<Row class="mx-1">
|
||||||
<Card class="px-0">
|
<Card class="px-0">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
- `cluster String`: The nodes' cluster
|
- `cluster String`: The nodes' cluster
|
||||||
- `subCluster String`: The nodes' subCluster
|
- `subCluster String`: The nodes' subCluster
|
||||||
- `hostname String`: The nodes' hostname
|
- `hostname String`: The nodes' hostname
|
||||||
- `dataHealth [Bool]`: Array of Booleans depicting state of returned data per metric
|
- `nodeState String`: The nodes current state as reported by the scheduler
|
||||||
|
- `metricHealth String`: The nodes current metric health as reported by the metricstore
|
||||||
- `nodeJobsData [Object]`: Data returned by GQL for jobs runninig on this node [Default: null]
|
- `nodeJobsData [Object]`: Data returned by GQL for jobs runninig on this node [Default: null]
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@@ -32,8 +33,8 @@
|
|||||||
cluster,
|
cluster,
|
||||||
subCluster,
|
subCluster,
|
||||||
hostname,
|
hostname,
|
||||||
hoststate,
|
nodeState,
|
||||||
dataHealth,
|
metricHealth,
|
||||||
nodeJobsData = null,
|
nodeJobsData = null,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
@@ -50,12 +51,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
// Not at least one returned, selected metric: NodeHealth warning
|
|
||||||
const fetchInfo = $derived(dataHealth.includes('fetching'));
|
|
||||||
// Not at least one returned, selected metric: NodeHealth warning
|
|
||||||
const healthWarn = $derived(!dataHealth.includes(true));
|
|
||||||
// At least one non-returned selected metric: Metric config error?
|
|
||||||
const metricWarn = $derived(dataHealth.includes(false));
|
|
||||||
const userList = $derived(nodeJobsData
|
const userList = $derived(nodeJobsData
|
||||||
? Array.from(new Set(nodeJobsData.jobs.items.map((j) => scrambleNames ? scramble(j.user) : j.user))).sort((a, b) => a.localeCompare(b))
|
? Array.from(new Set(nodeJobsData.jobs.items.map((j) => scrambleNames ? scramble(j.user) : j.user))).sort((a, b) => a.localeCompare(b))
|
||||||
: []
|
: []
|
||||||
@@ -86,14 +81,7 @@
|
|||||||
<Row cols={{xs: 1, lg: 2}}>
|
<Row cols={{xs: 1, lg: 2}}>
|
||||||
<Col class="mb-2 mb-lg-0">
|
<Col class="mb-2 mb-lg-0">
|
||||||
<InputGroup size="sm">
|
<InputGroup size="sm">
|
||||||
{#if fetchInfo}
|
{#if metricHealth == "failed"}
|
||||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
|
||||||
<Icon name="arrow-clockwise" style="padding-right: 0.5rem;"/>
|
|
||||||
</InputGroupText>
|
|
||||||
<Button class="flex-grow-1" color="dark" outline disabled>
|
|
||||||
Fetching
|
|
||||||
</Button>
|
|
||||||
{:else if healthWarn}
|
|
||||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<Icon name="exclamation-circle" style="padding-right: 0.5rem;"/>
|
<Icon name="exclamation-circle" style="padding-right: 0.5rem;"/>
|
||||||
<span>Info</span>
|
<span>Info</span>
|
||||||
@@ -101,13 +89,17 @@
|
|||||||
<Button class="flex-grow-1" color="danger" disabled>
|
<Button class="flex-grow-1" color="danger" disabled>
|
||||||
No Metrics
|
No Metrics
|
||||||
</Button>
|
</Button>
|
||||||
{:else if metricWarn}
|
{:else if metricHealth == "partial" || metricHealth == "unknown"}
|
||||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
<Icon name="info-circle" style="padding-right: 0.5rem;"/>
|
<Icon name="info-circle" style="padding-right: 0.5rem;"/>
|
||||||
<span>Info</span>
|
<span>Info</span>
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<Button class="flex-grow-1" color="warning" disabled>
|
<Button class="flex-grow-1" color="warning" disabled>
|
||||||
Missing Metric
|
{#if metricHealth == "partial"}
|
||||||
|
Missing Metric(s)
|
||||||
|
{:else if metricHealth == "unknown"}
|
||||||
|
Metric Health Unknown
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
{:else if nodeJobsData.jobs.count == 1 && nodeJobsData?.jobs?.items[0]?.shared == "none"}
|
{:else if nodeJobsData.jobs.count == 1 && nodeJobsData?.jobs?.items[0]?.shared == "none"}
|
||||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
@@ -150,8 +142,8 @@
|
|||||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||||
State
|
State
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<Button class="flex-grow-1" color={stateColors[hoststate]} disabled>
|
<Button class="flex-grow-1" color={stateColors[nodeState]} disabled>
|
||||||
{hoststate.charAt(0).toUpperCase() + hoststate.slice(1)}
|
{nodeState.charAt(0).toUpperCase() + nodeState.slice(1)}
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -75,7 +75,6 @@
|
|||||||
|
|
||||||
const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null);
|
const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null);
|
||||||
const refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(selectedMetrics, nodeData.metrics) : []);
|
const refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(selectedMetrics, nodeData.metrics) : []);
|
||||||
const dataHealth = $derived(refinedData.filter((rd) => rd.availability == "configured").map((enabled) => (nodeDataFetching ? 'fetching' : enabled?.data?.metric?.series?.length > 0)));
|
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
function sortAndSelectScope(metricList = [], nodeMetrics = []) {
|
function sortAndSelectScope(metricList = [], nodeMetrics = []) {
|
||||||
@@ -145,11 +144,12 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<NodeInfo
|
<NodeInfo
|
||||||
{cluster}
|
{cluster}
|
||||||
{dataHealth}
|
|
||||||
nodeJobsData={$nodeJobsData.data}
|
nodeJobsData={$nodeJobsData.data}
|
||||||
subCluster={nodeData.subCluster}
|
subCluster={nodeData.subCluster}
|
||||||
hostname={nodeData.host}
|
hostname={nodeData.host}
|
||||||
hoststate={nodeData?.state? nodeData.state: 'notindb'}/>
|
nodeState={nodeData?.nodeState || 'notindb'}
|
||||||
|
metricHealth={nodeData?.metricHealth || 'unknown'}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
{#each refinedData as metricData, i (metricData?.data?.name || i)}
|
{#each refinedData as metricData, i (metricData?.data?.name || i)}
|
||||||
|
|||||||
Reference in New Issue
Block a user