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:
@@ -130,7 +130,7 @@
|
||||
name
|
||||
count
|
||||
}
|
||||
# Get Current States fir Pie Charts
|
||||
# Get Current States for Pie Charts
|
||||
nodeStates(filter: $nodeFilter) {
|
||||
state
|
||||
count
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
|
||||
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
|
||||
host
|
||||
state
|
||||
nodeState
|
||||
metricHealth
|
||||
subCluster
|
||||
metrics {
|
||||
name
|
||||
@@ -92,7 +93,7 @@
|
||||
}
|
||||
}
|
||||
`;
|
||||
// Node State Colors
|
||||
// Node/Metric State Colors
|
||||
const stateColors = {
|
||||
allocated: 'success',
|
||||
reserved: 'info',
|
||||
@@ -100,7 +101,10 @@
|
||||
mixed: 'warning',
|
||||
down: 'danger',
|
||||
unknown: 'dark',
|
||||
notindb: 'secondary'
|
||||
notindb: 'secondary',
|
||||
full: 'success',
|
||||
partial: 'warning',
|
||||
failed: 'danger'
|
||||
}
|
||||
|
||||
/* 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>
|
||||
|
||||
<Row cols={{ xs: 2, lg: 5 }}>
|
||||
<Row cols={{ xs: 2, lg: 3}}>
|
||||
{#if $initq.error}
|
||||
<Card body color="danger">{$initq.error.message}</Card>
|
||||
{:else if $initq.fetching}
|
||||
<Spinner />
|
||||
{:else}
|
||||
<!-- Node Col -->
|
||||
<Col>
|
||||
<Col class="mb-2">
|
||||
<InputGroup>
|
||||
<InputGroupText><Icon name="hdd" /></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/>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<!-- State Col -->
|
||||
<Col>
|
||||
<!-- Node State Col -->
|
||||
<Col class="mb-2">
|
||||
<InputGroup>
|
||||
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
||||
<InputGroupText>Node State</InputGroupText>
|
||||
<Button class="flex-grow-1 text-center" color={stateColors[thisNodeState]} disabled>
|
||||
{#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}
|
||||
<span><Spinner size="sm" secondary/></span>
|
||||
{/if}
|
||||
@@ -185,7 +204,7 @@
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<!-- Concurrent Col -->
|
||||
<Col class="mt-2 mt-lg-0">
|
||||
<Col>
|
||||
{#if $nodeJobsData.fetching}
|
||||
<Spinner />
|
||||
{:else if $nodeJobsData.data}
|
||||
@@ -217,7 +236,7 @@
|
||||
/>
|
||||
</Col>
|
||||
<!-- Refresh Col-->
|
||||
<Col class="mt-2 mt-lg-0">
|
||||
<Col>
|
||||
<Refresher
|
||||
onRefresh={() => {
|
||||
const diff = Date.now() - to;
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
/* State Init */
|
||||
let hostnameFilter = $state("");
|
||||
let hoststateFilter = $state("all");
|
||||
let nodeStateFilter = $state("all");
|
||||
let pendingHostnameFilter = $state("");
|
||||
let isMetricsSelectionOpen = $state(false);
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
<InputGroup>
|
||||
<InputGroupText><Icon name="clipboard2-pulse" /></InputGroupText>
|
||||
<InputGroupText>State</InputGroupText>
|
||||
<Input type="select" bind:value={hoststateFilter}>
|
||||
<Input type="select" bind:value={nodeStateFilter}>
|
||||
{#each stateOptions as so}
|
||||
<option value={so}>{so.charAt(0).toUpperCase() + so.slice(1)}</option>
|
||||
{/each}
|
||||
@@ -269,11 +269,11 @@
|
||||
{:else}
|
||||
{#if displayNodeOverview}
|
||||
<!-- 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}
|
||||
<!-- ROW2-2: Node List (Grid Included)-->
|
||||
<NodeList pendingSelectedMetrics={selectedMetrics} {cluster} {subCluster}
|
||||
{selectedResolution} {hostnameFilter} {hoststateFilter} {from} {to} {systemUnits}/>
|
||||
{selectedResolution} {hostnameFilter} {nodeStateFilter} {from} {to} {systemUnits}/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
- `subCluster String`: The nodes' subCluster [Default: ""]
|
||||
- `pendingSelectedMetrics [String]`: The array of selected metrics [Default []]
|
||||
- `selectedResolution Number?`: The selected data resolution [Default: 0]
|
||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
||||
- `hoststateFilter String?`: The active hoststatefilter [Default: ""]
|
||||
- `hostnameFilter String?`: The active hostname filter [Default: ""]
|
||||
- `nodeStateFilter String?`: The active nodeState filter [Default: ""]
|
||||
- `systemUnits Object`: The object of metric units [Default: null]
|
||||
- `from Date?`: The selected "from" date [Default: null]
|
||||
- `to Date?`: The selected "to" date [Default: null]
|
||||
@@ -28,7 +28,7 @@
|
||||
pendingSelectedMetrics = [],
|
||||
selectedResolution = 0,
|
||||
hostnameFilter = "",
|
||||
hoststateFilter = "",
|
||||
nodeStateFilter = "",
|
||||
systemUnits = null,
|
||||
from = null,
|
||||
to = null
|
||||
@@ -54,7 +54,8 @@
|
||||
) {
|
||||
items {
|
||||
host
|
||||
state
|
||||
nodeState
|
||||
metricHealth
|
||||
subCluster
|
||||
metrics {
|
||||
name
|
||||
@@ -110,7 +111,7 @@
|
||||
variables: {
|
||||
cluster: cluster,
|
||||
subCluster: subCluster,
|
||||
stateFilter: hoststateFilter,
|
||||
stateFilter: nodeStateFilter,
|
||||
nodeFilter: hostnameFilter,
|
||||
scopes: ["core", "socket", "accelerator"],
|
||||
metrics: pendingSelectedMetrics,
|
||||
@@ -164,7 +165,7 @@
|
||||
|
||||
$effect(() => {
|
||||
// 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
|
||||
nodes = [];
|
||||
if (!usePaging) {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
||||
- `cluster String`: The cluster to show status information for
|
||||
- `selectedMetric String?`: The selectedMetric input [Default: ""]
|
||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
||||
- `hostnameFilter String?`: The active hoststatefilter [Default: ""]
|
||||
- `hostnameFilter String?`: The active hostname filter [Default: ""]
|
||||
- `nodeStateFilter String?`: The active nodeState filter [Default: ""]
|
||||
- `from Date?`: The selected "from" date [Default: null]
|
||||
- `to Date?`: The selected "to" date [Default: null]
|
||||
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
|
||||
@@ -24,7 +24,7 @@
|
||||
cluster = "",
|
||||
selectedMetric = "",
|
||||
hostnameFilter = "",
|
||||
hoststateFilter = "",
|
||||
nodeStateFilter = "",
|
||||
from = null,
|
||||
to = null,
|
||||
globalMetrics
|
||||
@@ -55,7 +55,7 @@
|
||||
to: $to
|
||||
) {
|
||||
host
|
||||
state
|
||||
nodeState
|
||||
subCluster
|
||||
metrics {
|
||||
name
|
||||
@@ -91,11 +91,11 @@
|
||||
const mappedData = $derived(handleQueryData($nodesQuery?.data));
|
||||
const filteredData = $derived(mappedData.filter((h) => {
|
||||
if (hostnameFilter) {
|
||||
if (hoststateFilter == 'all') return h.host.includes(hostnameFilter)
|
||||
else return (h.host.includes(hostnameFilter) && h.state == hoststateFilter)
|
||||
if (nodeStateFilter == 'all') return h.host.includes(hostnameFilter)
|
||||
else return (h.host.includes(hostnameFilter) && h.nodeState == nodeStateFilter)
|
||||
} else {
|
||||
if (hoststateFilter == 'all') return true
|
||||
else return h.state == hoststateFilter
|
||||
if (nodeStateFilter == 'all') return true
|
||||
else return h.nodeState == nodeStateFilter
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
if (rawData.length > 0) {
|
||||
pendingMapped = rawData.map((h) => ({
|
||||
host: h.host,
|
||||
state: h?.state? h.state : 'notindb',
|
||||
nodeState: h?.nodeState || 'notindb',
|
||||
subCluster: h.subCluster,
|
||||
data: h.metrics.filter(
|
||||
(m) => m?.name == selectedMetric && m.scope == "node",
|
||||
@@ -157,8 +157,8 @@
|
||||
>
|
||||
</h4>
|
||||
<span style="margin-right: 0.5rem;">
|
||||
<Badge color={stateColors[item?.state? item.state : 'notindb']}>
|
||||
State: {item?.state? item.state.charAt(0).toUpperCase() + item.state.slice(1) : 'Not in DB'}
|
||||
<Badge color={stateColors[item?.nodeState || 'notindb']}>
|
||||
State: {item?.nodeState ? item.nodeState.charAt(0).toUpperCase() + item.nodeState.slice(1) : 'Not in DB'}
|
||||
</Badge>
|
||||
</span>
|
||||
</div>
|
||||
@@ -202,7 +202,7 @@
|
||||
{/each}
|
||||
{/key}
|
||||
</Row>
|
||||
{:else if hostnameFilter || hoststateFilter != 'all'}
|
||||
{:else if hostnameFilter || nodeStateFilter != 'all'}
|
||||
<Row class="mx-1">
|
||||
<Card class="px-0">
|
||||
<CardHeader>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `subCluster String`: The nodes' subCluster
|
||||
- `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]
|
||||
-->
|
||||
|
||||
@@ -32,8 +33,8 @@
|
||||
cluster,
|
||||
subCluster,
|
||||
hostname,
|
||||
hoststate,
|
||||
dataHealth,
|
||||
nodeState,
|
||||
metricHealth,
|
||||
nodeJobsData = null,
|
||||
} = $props();
|
||||
|
||||
@@ -50,12 +51,6 @@
|
||||
}
|
||||
|
||||
/* 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
|
||||
? 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}}>
|
||||
<Col class="mb-2 mb-lg-0">
|
||||
<InputGroup size="sm">
|
||||
{#if fetchInfo}
|
||||
<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}
|
||||
{#if metricHealth == "failed"}
|
||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||
<Icon name="exclamation-circle" style="padding-right: 0.5rem;"/>
|
||||
<span>Info</span>
|
||||
@@ -101,13 +89,17 @@
|
||||
<Button class="flex-grow-1" color="danger" disabled>
|
||||
No Metrics
|
||||
</Button>
|
||||
{:else if metricWarn}
|
||||
{:else if metricHealth == "partial" || metricHealth == "unknown"}
|
||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||
<Icon name="info-circle" style="padding-right: 0.5rem;"/>
|
||||
<span>Info</span>
|
||||
</InputGroupText>
|
||||
<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>
|
||||
{:else if nodeJobsData.jobs.count == 1 && nodeJobsData?.jobs?.items[0]?.shared == "none"}
|
||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||
@@ -150,8 +142,8 @@
|
||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||
State
|
||||
</InputGroupText>
|
||||
<Button class="flex-grow-1" color={stateColors[hoststate]} disabled>
|
||||
{hoststate.charAt(0).toUpperCase() + hoststate.slice(1)}
|
||||
<Button class="flex-grow-1" color={stateColors[nodeState]} disabled>
|
||||
{nodeState.charAt(0).toUpperCase() + nodeState.slice(1)}
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
|
||||
const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null);
|
||||
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 */
|
||||
function sortAndSelectScope(metricList = [], nodeMetrics = []) {
|
||||
@@ -145,11 +144,12 @@
|
||||
{:else}
|
||||
<NodeInfo
|
||||
{cluster}
|
||||
{dataHealth}
|
||||
nodeJobsData={$nodeJobsData.data}
|
||||
subCluster={nodeData.subCluster}
|
||||
hostname={nodeData.host}
|
||||
hoststate={nodeData?.state? nodeData.state: 'notindb'}/>
|
||||
nodeState={nodeData?.nodeState || 'notindb'}
|
||||
metricHealth={nodeData?.metricHealth || 'unknown'}
|
||||
/>
|
||||
{/if}
|
||||
</td>
|
||||
{#each refinedData as metricData, i (metricData?.data?.name || i)}
|
||||
|
||||
Reference in New Issue
Block a user