improve nodeList loading indicator, streamlining

This commit is contained in:
Christoph Kluge
2026-02-12 14:27:41 +01:00
parent f016bd4232
commit 48729b172d
3 changed files with 83 additions and 73 deletions

View File

@@ -305,7 +305,7 @@
{#if $jobsStore.fetching || !$jobsStore.data} {#if $jobsStore.fetching || !$jobsStore.data}
<tr> <tr>
<td colspan={metrics.length + 1}> <td colspan={metrics.length + 1}>
<div style="text-align:center;"> <div style="text-align:center; margin-top: 1rem;">
<Spinner secondary /> <Spinner secondary />
</div> </div>
</td> </td>

View File

@@ -104,7 +104,7 @@
let itemsPerPage = $derived(usePaging ? (ccconfig?.nodeList_nodesPerPage || 10) : 10); let itemsPerPage = $derived(usePaging ? (ccconfig?.nodeList_nodesPerPage || 10) : 10);
let paging = $derived({ itemsPerPage, page }); let paging = $derived({ itemsPerPage, page });
const nodesQuery = $derived(queryStore({ const nodesStore = $derived(queryStore({
client: client, client: client,
query: nodeListQuery, query: nodeListQuery,
variables: { variables: {
@@ -122,7 +122,7 @@
requestPolicy: "network-only", // Resolution queries are cached, but how to access them? For now: reload on every change requestPolicy: "network-only", // Resolution queries are cached, but how to access them? For now: reload on every change
})); }));
const matchedNodes = $derived($nodesQuery?.data?.nodeMetricsList?.totalNodes || 0); const matchedNodes = $derived($nodesStore?.data?.nodeMetricsList?.totalNodes || 0);
/* Effects */ /* Effects */
$effect(() => { $effect(() => {
@@ -135,7 +135,7 @@
} = document.documentElement; } = document.documentElement;
// Add 100 px offset to trigger load earlier // Add 100 px offset to trigger load earlier
if (scrollTop + clientHeight >= scrollHeight - 100 && $nodesQuery?.data?.nodeMetricsList?.hasNextPage) { if (scrollTop + clientHeight >= scrollHeight - 100 && $nodesStore?.data?.nodeMetricsList?.hasNextPage) {
page += 1 page += 1
}; };
}); });
@@ -143,9 +143,9 @@
}); });
$effect(() => { $effect(() => {
if ($nodesQuery?.data) { if ($nodesStore?.data) {
untrack(() => { untrack(() => {
handleNodes($nodesQuery?.data?.nodeMetricsList?.items); handleNodes($nodesStore?.data?.nodeMetricsList?.items);
}); });
selectedMetrics = [...pendingSelectedMetrics]; // Trigger Rerender in NodeListRow Only After Data is Fetched selectedMetrics = [...pendingSelectedMetrics]; // Trigger Rerender in NodeListRow Only After Data is Fetched
}; };
@@ -228,7 +228,7 @@
style="padding-top: {headerPaddingTop}px;" style="padding-top: {headerPaddingTop}px;"
> >
{cluster} Node Info {cluster} Node Info
{#if $nodesQuery.fetching} {#if $nodesStore.fetching}
<Spinner size="sm" style="margin-left:10px;" secondary /> <Spinner size="sm" style="margin-left:10px;" secondary />
{/if} {/if}
</th> </th>
@@ -245,22 +245,22 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#if $nodesQuery.error} {#if $nodesStore.error}
<Row> <Row>
<Col> <Col>
<Card body color="danger">{$nodesQuery.error.message}</Card> <Card body color="danger">{$nodesStore.error.message}</Card>
</Col> </Col>
</Row> </Row>
{:else} {:else}
{#each nodes as nodeData (nodeData.host)} {#each nodes as nodeData (nodeData.host)}
<NodeListRow {nodeData} {cluster} {selectedMetrics} {globalMetrics}/> <NodeListRow {nodeData} {cluster} {selectedMetrics} {globalMetrics} nodeDataFetching={$nodesStore.fetching}/>
{:else} {:else}
<tr> <tr>
<td colspan={selectedMetrics.length + 1}> No nodes found </td> <td colspan={selectedMetrics.length + 1}> No nodes found </td>
</tr> </tr>
{/each} {/each}
{/if} {/if}
{#if $nodesQuery.fetching || !$nodesQuery.data} {#if $nodesStore.fetching || !$nodesStore.data}
<tr> <tr>
<td colspan={pendingSelectedMetrics.length + 1}> <td colspan={pendingSelectedMetrics.length + 1}>
<div style="text-align:center;"> <div style="text-align:center;">

View File

@@ -4,6 +4,7 @@
Properties: Properties:
- `cluster String`: The nodes' cluster - `cluster String`: The nodes' cluster
- `nodeData Object`: The node data object including metric data - `nodeData Object`: The node data object including metric data
- `nodeDataFetching Bool`: Whether the metric query still runs
- `selectedMetrics [String]`: The array of selected metrics - `selectedMetrics [String]`: The array of selected metrics
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster - `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
--> -->
@@ -24,6 +25,7 @@
let { let {
cluster, cluster,
nodeData, nodeData,
nodeDataFetching,
selectedMetrics, selectedMetrics,
globalMetrics globalMetrics
} = $props(); } = $props();
@@ -72,7 +74,7 @@
); );
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(!nodeDataFetching ? 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) => (enabled?.data?.metric?.series?.length > 0)));
/* Functions */ /* Functions */
@@ -150,65 +152,73 @@
hoststate={nodeData?.state? nodeData.state: 'notindb'}/> hoststate={nodeData?.state? nodeData.state: 'notindb'}/>
{/if} {/if}
</td> </td>
{#each refinedData as metricData, i (metricData?.data?.name || i)} {#if nodeDataFetching}
{#key metricData} <td colspan={selectedMetrics.length}>
<td> <div style="text-align:center; margin-top: 1rem;">
{#if metricData?.availability == "none"} <Spinner secondary />
<Card body class="mx-2" color="light"> </div>
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> </td>
<p class="mb-1">Metric is not configured for cluster <b>{cluster}</b>.</p> {:else}
</Card> {#each refinedData as metricData, i (metricData?.data?.name || i)}
{:else if metricData?.availability == "disabled"} {#key metricData}
<Card body class="mx-2" color="info"> <td>
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> {#if metricData?.availability == "none"}
<p class="mb-1">Metric has been disabled for subcluster <b>{nodeData.subCluster}</b>.</p> <Card body class="mx-2" color="light">
</Card> <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
{:else if !metricData?.data} <p class="mb-1">Metric is not configured 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?.availability == "disabled"}
<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="info">
</Card> <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
{:else if !!metricData.data?.metric.statisticsSeries} <p class="mb-1">Metric has been disabled for subcluster <b>{nodeData.subCluster}</b>.</p>
<!-- "No Data"-Warning included in MetricPlot-Component --> </Card>
<MetricPlot {:else if !metricData?.data}
{cluster} <Card body class="mx-2" color="warning">
subCluster={nodeData.subCluster} <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
metric={metricData.data.name} <p class="mb-1">Metric or host was not found in metric store for cluster <b>{cluster}</b>.</p>
scope={metricData.data.scope} </Card>
timestep={metricData.data.metric.timestep} {:else if !!metricData.data?.metric.statisticsSeries}
series={metricData.data.metric.series} <!-- "No Data"-Warning included in MetricPlot-Component -->
statisticsSeries={metricData.data?.metric.statisticsSeries} <MetricPlot
useStatsSeries={!!metricData.data?.metric.statisticsSeries} {cluster}
height={175} subCluster={nodeData.subCluster}
{plotSync} metric={metricData.data.name}
forNode scope={metricData.data.scope}
/> timestep={metricData.data.metric.timestep}
<div class="my-2"></div> series={metricData.data.metric.series}
<MetricPlot statisticsSeries={metricData.data?.metric.statisticsSeries}
{cluster} useStatsSeries={!!metricData.data?.metric.statisticsSeries}
subCluster={nodeData.subCluster} height={175}
metric={metricData.data.name} {plotSync}
scope={metricData.data.scope} forNode
timestep={metricData.data.metric.timestep} />
series={metricData.data.metric.series} <div class="my-2"></div>
height={175} <MetricPlot
{extendedLegendData} {cluster}
{plotSync} subCluster={nodeData.subCluster}
forNode metric={metricData.data.name}
/> scope={metricData.data.scope}
{:else} timestep={metricData.data.metric.timestep}
<MetricPlot series={metricData.data.metric.series}
{cluster} height={175}
subCluster={nodeData.subCluster} {extendedLegendData}
metric={metricData.data.name} {plotSync}
scope={metricData.data.scope} forNode
timestep={metricData.data.metric.timestep} />
series={metricData.data.metric.series} {:else}
height={375} <MetricPlot
forNode {cluster}
/> subCluster={nodeData.subCluster}
{/if} metric={metricData.data.name}
</td> scope={metricData.data.scope}
{/key} timestep={metricData.data.metric.timestep}
{/each} series={metricData.data.metric.series}
height={375}
forNode
/>
{/if}
</td>
{/key}
{/each}
{/if}
</tr> </tr>