add not configured info cards, show short job filter options if one active filter

This commit is contained in:
Christoph Kluge
2026-02-10 13:49:23 +01:00
parent 0dff9fa07f
commit 49a1748641
9 changed files with 105 additions and 63 deletions

2
go.sum
View File

@@ -4,8 +4,6 @@ github.com/99designs/gqlgen v0.17.85 h1:EkGx3U2FDcxQm8YDLQSpXIAVmpDyZ3IcBMOJi2nH
github.com/99designs/gqlgen v0.17.85/go.mod h1:yvs8s0bkQlRfqg03YXr3eR4OQUowVhODT/tHzCXnbOU= github.com/99designs/gqlgen v0.17.85/go.mod h1:yvs8s0bkQlRfqg03YXr3eR4OQUowVhODT/tHzCXnbOU=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/ClusterCockpit/cc-lib/v2 v2.2.1 h1:iCVas+Jc61zFH5S2VG3H1sc7tsn+U4lOJwUYjYZEims=
github.com/ClusterCockpit/cc-lib/v2 v2.2.1/go.mod h1:JuxMAuEOaLLNEnnL9U3ejha8kMvsSatLdKPZEgJw6iw=
github.com/ClusterCockpit/cc-lib/v2 v2.2.2 h1:ye4RY57I19c2cXr3XWZBS/QYYgQVeGFvsiu5HkyKq9E= github.com/ClusterCockpit/cc-lib/v2 v2.2.2 h1:ye4RY57I19c2cXr3XWZBS/QYYgQVeGFvsiu5HkyKq9E=
github.com/ClusterCockpit/cc-lib/v2 v2.2.2/go.mod h1:JuxMAuEOaLLNEnnL9U3ejha8kMvsSatLdKPZEgJw6iw= github.com/ClusterCockpit/cc-lib/v2 v2.2.2/go.mod h1:JuxMAuEOaLLNEnnL9U3ejha8kMvsSatLdKPZEgJw6iw=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=

View File

@@ -30,7 +30,7 @@
import { import {
init, init,
groupByScope, groupByScope,
checkMetricDisabled, checkMetricAvailability,
} from "./generic/utils.js"; } from "./generic/utils.js";
import Metric from "./job/Metric.svelte"; import Metric from "./job/Metric.svelte";
import MetricSelection from "./generic/select/MetricSelection.svelte"; import MetricSelection from "./generic/select/MetricSelection.svelte";
@@ -151,17 +151,17 @@
} }
return names; return names;
}, []); }, []);
//
return metricNames.filter( return metricNames.filter(
(metric) => (metric) =>
!metrics.some((jm) => jm.name == metric) && !metrics.some((jm) => jm.name == metric) &&
selectedMetrics.includes(metric) && selectedMetrics.includes(metric) &&
!checkMetricDisabled( (checkMetricAvailability(
globalMetrics, globalMetrics,
metric, metric,
thisJob.cluster, thisJob.cluster,
thisJob.subCluster, thisJob.subCluster,
), ) == "configured")
); );
} else { } else {
return [] return []
@@ -212,7 +212,7 @@
inputMetrics.map((metric) => ({ inputMetrics.map((metric) => ({
metric: metric, metric: metric,
data: grouped.find((group) => group[0].name == metric), data: grouped.find((group) => group[0].name == metric),
disabled: checkMetricDisabled( availability: checkMetricAvailability(
globalMetrics, globalMetrics,
metric, metric,
thisJob.cluster, thisJob.cluster,
@@ -333,7 +333,17 @@
{:else if thisJob && $jobMetrics?.data?.scopedJobStats} {:else if thisJob && $jobMetrics?.data?.scopedJobStats}
<!-- Note: Ignore '#snippet' Error in IDE --> <!-- Note: Ignore '#snippet' Error in IDE -->
{#snippet gridContent(item)} {#snippet gridContent(item)}
{#if item?.disabled} {#if item.availability == "none"}
<Card color="light" class="mt-2">
<CardHeader class="mb-0">
<b>Metric not configured</b>
</CardHeader>
<CardBody>
<p>No datasets returned for <b>{item.metric}</b>.</p>
<p class="mb-1">Metric is not configured for cluster <b>{thisJob.cluster}</b>.</p>
</CardBody>
</Card>
{:else if item.availability == "disabled"}
<Card color="info" class="mt-2"> <Card color="info" class="mt-2">
<CardHeader class="mb-0"> <CardHeader class="mb-0">
<b>Disabled Metric</b> <b>Disabled Metric</b>

View File

@@ -142,7 +142,8 @@
<Filters <Filters
bind:this={filterComponent} bind:this={filterComponent}
{filterPresets} {filterPresets}
shortJobQuickSelect startTimeQuickSelect
shortJobQuickSelect={(filterBuffer.length > 0)}
shortJobCutoff={ccconfig?.jobList_hideShortRunningJobs} shortJobCutoff={ccconfig?.jobList_hideShortRunningJobs}
showFilter={!showCompare} showFilter={!showCompare}
matchedJobs={showCompare? matchedCompareJobs: matchedListJobs} matchedJobs={showCompare? matchedCompareJobs: matchedListJobs}

View File

@@ -32,7 +32,7 @@
} from "@urql/svelte"; } from "@urql/svelte";
import { import {
init, init,
checkMetricDisabled, checkMetricAvailability,
} from "./generic/utils.js"; } from "./generic/utils.js";
import PlotGrid from "./generic/PlotGrid.svelte"; import PlotGrid from "./generic/PlotGrid.svelte";
import MetricPlot from "./generic/plots/MetricPlot.svelte"; import MetricPlot from "./generic/plots/MetricPlot.svelte";
@@ -242,17 +242,17 @@
{item.name} {item.name}
{systemUnits[item.name] ? "(" + systemUnits[item.name] + ")" : ""} {systemUnits[item.name] ? "(" + systemUnits[item.name] + ")" : ""}
</h4> </h4>
{#if item.disabled === false && item.metric} {#if item.availability == "none"}
<MetricPlot <Card color="light" class="mx-2">
metric={item.name} <CardHeader class="mb-0">
timestep={item.metric.timestep} <b>Metric not configured</b>
cluster={clusterInfos.find((c) => c.name == cluster)} </CardHeader>
subCluster={$nodeMetricsData.data.nodeMetrics[0].subCluster} <CardBody>
series={item.metric.series} <p>No datasets returned for <b>{item.name}</b>.</p>
enableFlip <p class="mb-1">Metric is not configured for cluster <b>{cluster}</b>.</p>
forNode </CardBody>
/> </Card>
{:else if item.disabled === true && item.metric} {:else if item.availability == "disabled"}
<Card color="info" class="mx-2"> <Card color="info" class="mx-2">
<CardHeader class="mb-0"> <CardHeader class="mb-0">
<b>Disabled Metric</b> <b>Disabled Metric</b>
@@ -262,6 +262,16 @@
<p class="mb-1">Metric has been disabled for subcluster <b>{$nodeMetricsData.data.nodeMetrics[0].subCluster}</b>.</p> <p class="mb-1">Metric has been disabled for subcluster <b>{$nodeMetricsData.data.nodeMetrics[0].subCluster}</b>.</p>
</CardBody> </CardBody>
</Card> </Card>
{:else if item?.metric}
<MetricPlot
metric={item.name}
timestep={item.metric.timestep}
cluster={clusterInfos.find((c) => c.name == cluster)}
subCluster={$nodeMetricsData.data.nodeMetrics[0].subCluster}
series={item.metric.series}
enableFlip
forNode
/>
{:else} {:else}
<Card color="warning" class="mx-2"> <Card color="warning" class="mx-2">
<CardHeader class="mb-0"> <CardHeader class="mb-0">
@@ -279,7 +289,7 @@
items={$nodeMetricsData.data.nodeMetrics[0].metrics items={$nodeMetricsData.data.nodeMetrics[0].metrics
.map((m) => ({ .map((m) => ({
...m, ...m,
disabled: checkMetricDisabled( availability: checkMetricAvailability(
globalMetrics, globalMetrics,
m.name, m.name,
cluster, cluster,

View File

@@ -219,7 +219,8 @@
<Filters <Filters
bind:this={filterComponent} bind:this={filterComponent}
{filterPresets} {filterPresets}
shortJobQuickSelect startTimeQuickSelect
shortJobQuickSelect={(filterBuffer.length > 0)}
shortJobCutoff={ccconfig?.jobList_hideShortRunningJobs} shortJobCutoff={ccconfig?.jobList_hideShortRunningJobs}
showFilter={!showCompare} showFilter={!showCompare}
matchedJobs={showCompare? matchedCompareJobs: matchedListJobs} matchedJobs={showCompare? matchedCompareJobs: matchedListJobs}

View File

@@ -19,7 +19,7 @@
<script> <script>
import { queryStore, gql, getContextClient } from "@urql/svelte"; import { queryStore, gql, getContextClient } from "@urql/svelte";
import { Card, Spinner } from "@sveltestrap/sveltestrap"; import { Card, Spinner } from "@sveltestrap/sveltestrap";
import { maxScope, checkMetricDisabled } from "../utils.js"; import { maxScope, checkMetricAvailability } from "../utils.js";
import JobInfo from "./JobInfo.svelte"; import JobInfo from "./JobInfo.svelte";
import MetricPlot from "../plots/MetricPlot.svelte"; import MetricPlot from "../plots/MetricPlot.svelte";
import JobFootprint from "../helper/JobFootprint.svelte"; import JobFootprint from "../helper/JobFootprint.svelte";
@@ -145,7 +145,7 @@
metricList.forEach((metricName) => { metricList.forEach((metricName) => {
const pendingMetric = { const pendingMetric = {
name: metricName, name: metricName,
disabled: checkMetricDisabled( availability: checkMetricAvailability(
globalMetrics, globalMetrics,
metricName, metricName,
job.cluster, job.cluster,
@@ -207,7 +207,12 @@
{/if} {/if}
{#each refinedData as metric, i (metric?.name || i)} {#each refinedData as metric, i (metric?.name || i)}
<td> <td>
{#if metric?.disabled} {#if metric?.availability == "none"}
<Card body class="mx-2" color="light">
<p>No dataset(s) returned for <b>{metrics[i]}</b></p>
<p class="mb-1">Metric is not configured for cluster <b>{job.cluster}</b>.</p>
</Card>
{:else if metric?.availability == "disabled"}
<Card body class="mx-2" color="info"> <Card body class="mx-2" color="info">
<p>No dataset(s) returned for <b>{metrics[i]}</b></p> <p>No dataset(s) returned for <b>{metrics[i]}</b></p>
<p class="mb-1">Metric has been disabled for subcluster <b>{job.subCluster}</b>.</p> <p class="mb-1">Metric has been disabled for subcluster <b>{job.subCluster}</b>.</p>

View File

@@ -302,20 +302,36 @@ export function stickyHeader(datatableHeaderSelector, updatePading) {
onDestroy(() => document.removeEventListener("scroll", onscroll)); onDestroy(() => document.removeEventListener("scroll", onscroll));
} }
export function checkMetricDisabled(gm, m, c, s) { // [g]lobal[m]etrics, [m]etric, [c]luster, [s]ubcluster export function checkMetricAvailability(gms, m, c, s = "") { // [g]lobal[m]etrics, [m]etric, [c]luster, [s]ubcluster
const available = gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s) let pendingAvailability = "none"
// Return inverse logic const configured = gms?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)
return !available if (configured) {
pendingAvailability = "configured"
if (s != "") {
const enabled = configured.subClusters?.includes(s)
// Test inverse logic
if (!enabled) {
pendingAvailability = "disabled"
}
}
}
return pendingAvailability;
} }
export function checkMetricsDisabled(gm, ma, c, s) { // [g]lobal[m]etrics, [m]etric[a]rray, [c]luster, [s]ubcluster // export function checkMetricDisabled(gm, m, c, s) { // [g]lobal[m]etrics, [m]etric, [c]luster, [s]ubcluster
let result = {}; // const available = gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s)
ma.forEach((m) => { // // Return inverse logic
// Return named inverse logic: !available // return !available
result[m] = !(gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s)) // }
});
return result // export function checkMetricsDisabled(gm, ma, c, s) { // [g]lobal[m]etrics, [m]etric[a]rray, [c]luster, [s]ubcluster
} // let aresult = {};
// ma.forEach((m) => {
// // Return named inverse logic: !available
// aresult[m] = !(gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s))
// });
// return aresult
// }
export function getStatsItems(presetStats = []) { export function getStatsItems(presetStats = []) {
// console.time('stats') // console.time('stats')

View File

@@ -15,7 +15,7 @@
<script> <script>
import { queryStore, gql, getContextClient } from "@urql/svelte"; import { queryStore, gql, getContextClient } from "@urql/svelte";
import { Row, Col, Card, CardHeader, CardBody, Spinner, Badge } from "@sveltestrap/sveltestrap"; import { Row, Col, Card, CardHeader, CardBody, Spinner, Badge } from "@sveltestrap/sveltestrap";
import { checkMetricDisabled } from "../generic/utils.js"; import { checkMetricAvailability } from "../generic/utils.js";
import MetricPlot from "../generic/plots/MetricPlot.svelte"; import MetricPlot from "../generic/plots/MetricPlot.svelte";
/* Svelte 5 Props */ /* Svelte 5 Props */
@@ -87,6 +87,7 @@
}, },
})); }));
const notConfigured = $derived(checkMetricAvailability(globalMetrics, selectedMetric, cluster) == "none");
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) {
@@ -120,7 +121,7 @@
data: h.metrics.filter( data: h.metrics.filter(
(m) => m?.name == selectedMetric && m.scope == "node", (m) => m?.name == selectedMetric && m.scope == "node",
), ),
disabled: checkMetricDisabled(globalMetrics, selectedMetric, cluster, h.subCluster), availability: checkMetricAvailability(globalMetrics, selectedMetric, cluster, h.subCluster),
})) }))
.sort((a, b) => a.host.localeCompare(b.host)) .sort((a, b) => a.host.localeCompare(b.host))
} }
@@ -161,7 +162,7 @@
</Badge> </Badge>
</span> </span>
</div> </div>
{#if item?.disabled} {#if item?.availability == "disabled"}
<Card color="info"> <Card color="info">
<CardHeader class="mb-0"> <CardHeader class="mb-0">
<b>Disabled Metric</b> <b>Disabled Metric</b>
@@ -213,6 +214,18 @@
</CardBody> </CardBody>
</Card> </Card>
</Row> </Row>
{:else if notConfigured}
<Row class="mx-1">
<Card class="px-0" color="light">
<CardHeader>
<b>Metric not configured</b>
</CardHeader>
<CardBody>
<p>No datasets returned for <b>{selectedMetric}</b>.</p>
<p class="mb-1">Metric is not configured for cluster <b>{cluster}</b>.</p>
</CardBody>
</Card>
</Row>
{:else} {:else}
<Row class="mx-1"> <Row class="mx-1">
<Card class="px-0" color="warning"> <Card class="px-0" color="warning">

View File

@@ -16,7 +16,7 @@
} from "@urql/svelte"; } from "@urql/svelte";
import uPlot from "uplot"; import uPlot from "uplot";
import { Card, CardBody, Spinner } from "@sveltestrap/sveltestrap"; import { Card, CardBody, Spinner } from "@sveltestrap/sveltestrap";
import { maxScope, checkMetricDisabled, scramble, scrambleNames } from "../../generic/utils.js"; import { maxScope, checkMetricAvailability, scramble, scrambleNames } from "../../generic/utils.js";
import MetricPlot from "../../generic/plots/MetricPlot.svelte"; import MetricPlot from "../../generic/plots/MetricPlot.svelte";
import NodeInfo from "./NodeInfo.svelte"; import NodeInfo from "./NodeInfo.svelte";
@@ -73,7 +73,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(nodeData?.metrics ? sortAndSelectScope(selectedMetrics, nodeData.metrics) : []);
const dataHealth = $derived(refinedData.filter((rd) => rd.disabled === false).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 */
function sortAndSelectScope(metricList = [], nodeMetrics = []) { function sortAndSelectScope(metricList = [], nodeMetrics = []) {
@@ -81,7 +81,7 @@
metricList.forEach((metricName) => { metricList.forEach((metricName) => {
const pendingMetric = { const pendingMetric = {
name: metricName, name: metricName,
disabled: checkMetricDisabled( availability: checkMetricAvailability(
globalMetrics, globalMetrics,
metricName, metricName,
cluster, cluster,
@@ -130,23 +130,6 @@
return pendingExtendedLegendData; return pendingExtendedLegendData;
} }
/* Inspect */
// $inspect(selectedMetrics).with((type, selectedMetrics) => {
// console.log(type, 'selectedMetrics', selectedMetrics)
// });
// $inspect(nodeData).with((type, nodeData) => {
// console.log(type, 'nodeData', nodeData)
// });
// $inspect(refinedData).with((type, refinedData) => {
// console.log(type, 'refinedData', refinedData)
// });
// $inspect(dataHealth).with((type, dataHealth) => {
// console.log(type, 'dataHealth', dataHealth)
// });
</script> </script>
<tr> <tr>
@@ -169,7 +152,12 @@
</td> </td>
{#each refinedData as metricData, i (metricData?.data?.name || i)} {#each refinedData as metricData, i (metricData?.data?.name || i)}
<td> <td>
{#if metricData?.disabled} {#if metricData?.availability == "none"}
<Card body class="mx-2" color="light">
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
<p class="mb-1">Metric is not configured for cluster <b>{cluster}</b>.</p>
</Card>
{:else if metricData?.availability == "disabled"}
<Card body class="mx-2" color="info"> <Card body class="mx-2" color="info">
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
<p class="mb-1">Metric has been disabled for subcluster <b>{nodeData.subCluster}</b>.</p> <p class="mb-1">Metric has been disabled for subcluster <b>{nodeData.subCluster}</b>.</p>
@@ -177,7 +165,7 @@
{:else if !metricData?.data} {:else if !metricData?.data}
<Card body class="mx-2" color="warning"> <Card body class="mx-2" color="warning">
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
<p class="mb-1">Metric was not found in metric store for cluster <b>{cluster}</b>.</p> <p class="mb-1">Metric or host was not found in metric store for cluster <b>{cluster}</b>.</p>
</Card> </Card>
{:else if !!metricData.data?.metric.statisticsSeries} {:else if !!metricData.data?.metric.statisticsSeries}
<!-- "No Data"-Warning included in MetricPlot-Component --> <!-- "No Data"-Warning included in MetricPlot-Component -->