Merge branch 'dev' of github.com:ClusterCockpit/cc-backend into dev

This commit is contained in:
2026-02-13 12:19:33 +01:00
3 changed files with 95 additions and 79 deletions

View File

@@ -152,12 +152,21 @@
}); });
$effect(() => { $effect(() => {
// Triggers (Except Paging) // Update NodeListRows metrics only: Keep ordered nodes on page 1
from, to from, to
pendingSelectedMetrics, selectedResolution pendingSelectedMetrics, selectedResolution
// Continous Scroll: Paging if parameters change: Existing entries will not match new selections
if (!usePaging) {
nodes = [];
page = 1;
}
});
$effect(() => {
// Update NodeListRows metrics only: Keep ordered nodes on page 1
hostnameFilter, hoststateFilter hostnameFilter, hoststateFilter
// 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 Array Reset in HandleNodes func nodes = [];
if (!usePaging) { if (!usePaging) {
page = 1; page = 1;
} }
@@ -255,9 +264,11 @@
{#each nodes as nodeData (nodeData.host)} {#each nodes as nodeData (nodeData.host)}
<NodeListRow {nodeData} {cluster} {selectedMetrics} {globalMetrics} nodeDataFetching={$nodesStore.fetching}/> <NodeListRow {nodeData} {cluster} {selectedMetrics} {globalMetrics} nodeDataFetching={$nodesStore.fetching}/>
{:else} {:else}
<tr> {#if !$nodesStore.fetching}
<td colspan={selectedMetrics.length + 1}> No nodes found </td> <tr>
</tr> <td colspan={selectedMetrics.length + 1}> No nodes found </td>
</tr>
{/if}
{/each} {/each}
{/if} {/if}
{#if $nodesStore.fetching || !$nodesStore.data} {#if $nodesStore.fetching || !$nodesStore.data}

View File

@@ -51,6 +51,8 @@
/* Derived */ /* Derived */
// Not at least one returned, selected metric: NodeHealth warning // 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)); const healthWarn = $derived(!dataHealth.includes(true));
// At least one non-returned selected metric: Metric config error? // At least one non-returned selected metric: Metric config error?
const metricWarn = $derived(dataHealth.includes(false)); const metricWarn = $derived(dataHealth.includes(false));
@@ -84,10 +86,17 @@
<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 healthWarn} {#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}
<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>Jobs</span> <span>Info</span>
</InputGroupText> </InputGroupText>
<Button class="flex-grow-1" color="danger" disabled> <Button class="flex-grow-1" color="danger" disabled>
No Metrics No Metrics
@@ -95,7 +104,7 @@
{:else if metricWarn} {:else if metricWarn}
<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>Jobs</span> <span>Info</span>
</InputGroupText> </InputGroupText>
<Button class="flex-grow-1" color="warning" disabled> <Button class="flex-grow-1" color="warning" disabled>
Missing Metric Missing Metric

View File

@@ -74,8 +74,8 @@
); );
const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null); const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null);
const refinedData = $derived(!nodeDataFetching ? sortAndSelectScope(selectedMetrics, nodeData.metrics) : []); const refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(selectedMetrics, nodeData.metrics) : []);
const dataHealth = $derived(refinedData.filter((rd) => rd.availability == "configured").map((enabled) => (enabled?.data?.metric?.series?.length > 0))); 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 = []) {
@@ -152,73 +152,69 @@
hoststate={nodeData?.state? nodeData.state: 'notindb'}/> hoststate={nodeData?.state? nodeData.state: 'notindb'}/>
{/if} {/if}
</td> </td>
{#if nodeDataFetching} {#each refinedData as metricData, i (metricData?.data?.name || i)}
<td colspan={selectedMetrics.length}> {#key metricData}
<div style="text-align:center; margin-top: 1rem;"> <td>
<Spinner secondary /> {#if !metricData?.data && nodeDataFetching}
</div> <div style="text-align:center; margin-top: 1rem;">
</td> <Spinner secondary />
{:else} </div>
{#each refinedData as metricData, i (metricData?.data?.name || i)} {:else if metricData?.availability == "none"}
{#key metricData} <Card body class="mx-2" color="light">
<td> <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
{#if metricData?.availability == "none"} <p class="mb-1">Metric is not configured for cluster <b>{cluster}</b>.</p>
<Card body class="mx-2" color="light"> </Card>
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> {:else if metricData?.availability == "disabled"}
<p class="mb-1">Metric is not configured for cluster <b>{cluster}</b>.</p> <Card body class="mx-2" color="info">
</Card> <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
{:else if metricData?.availability == "disabled"} <p class="mb-1">Metric has been disabled for subcluster <b>{nodeData.subCluster}</b>.</p>
<Card body class="mx-2" color="info"> </Card>
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> {:else if !metricData?.data}
<p class="mb-1">Metric has been disabled for subcluster <b>{nodeData.subCluster}</b>.</p> <Card body class="mx-2" color="warning">
</Card> <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
{:else if !metricData?.data} <p class="mb-1">Metric or host was not found in metric store for cluster <b>{cluster}</b>.</p>
<Card body class="mx-2" color="warning"> </Card>
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> {:else if !!metricData.data?.metric.statisticsSeries}
<p class="mb-1">Metric or host was not found in metric store for cluster <b>{cluster}</b>.</p> <!-- "No Data"-Warning included in MetricPlot-Component -->
</Card> <MetricPlot
{:else if !!metricData.data?.metric.statisticsSeries} {cluster}
<!-- "No Data"-Warning included in MetricPlot-Component --> subCluster={nodeData.subCluster}
<MetricPlot metric={metricData.data.name}
{cluster} scope={metricData.data.scope}
subCluster={nodeData.subCluster} timestep={metricData.data.metric.timestep}
metric={metricData.data.name} series={metricData.data.metric.series}
scope={metricData.data.scope} statisticsSeries={metricData.data?.metric.statisticsSeries}
timestep={metricData.data.metric.timestep} useStatsSeries={!!metricData.data?.metric.statisticsSeries}
series={metricData.data.metric.series} height={175}
statisticsSeries={metricData.data?.metric.statisticsSeries} {plotSync}
useStatsSeries={!!metricData.data?.metric.statisticsSeries} forNode
height={175} />
{plotSync} <div class="my-2"></div>
forNode <MetricPlot
/> {cluster}
<div class="my-2"></div> subCluster={nodeData.subCluster}
<MetricPlot metric={metricData.data.name}
{cluster} scope={metricData.data.scope}
subCluster={nodeData.subCluster} timestep={metricData.data.metric.timestep}
metric={metricData.data.name} series={metricData.data.metric.series}
scope={metricData.data.scope} height={175}
timestep={metricData.data.metric.timestep} {extendedLegendData}
series={metricData.data.metric.series} {plotSync}
height={175} forNode
{extendedLegendData} />
{plotSync} {:else}
forNode <MetricPlot
/> {cluster}
{:else} subCluster={nodeData.subCluster}
<MetricPlot metric={metricData.data.name}
{cluster} scope={metricData.data.scope}
subCluster={nodeData.subCluster} timestep={metricData.data.metric.timestep}
metric={metricData.data.name} series={metricData.data.metric.series}
scope={metricData.data.scope} height={375}
timestep={metricData.data.metric.timestep} forNode
series={metricData.data.metric.series} />
height={375} {/if}
forNode </td>
/> {/key}
{/if} {/each}
</td>
{/key}
{/each}
{/if}
</tr> </tr>