mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-02-18 08:51:45 +01:00
Merge branch 'dev' into log-aggregator
This commit is contained in:
@@ -30,7 +30,7 @@
|
||||
import {
|
||||
init,
|
||||
groupByScope,
|
||||
checkMetricDisabled,
|
||||
checkMetricAvailability,
|
||||
} from "./generic/utils.js";
|
||||
import Metric from "./job/Metric.svelte";
|
||||
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
||||
@@ -151,17 +151,17 @@
|
||||
}
|
||||
return names;
|
||||
}, []);
|
||||
|
||||
//
|
||||
return metricNames.filter(
|
||||
(metric) =>
|
||||
!metrics.some((jm) => jm.name == metric) &&
|
||||
selectedMetrics.includes(metric) &&
|
||||
!checkMetricDisabled(
|
||||
(checkMetricAvailability(
|
||||
globalMetrics,
|
||||
metric,
|
||||
thisJob.cluster,
|
||||
thisJob.subCluster,
|
||||
),
|
||||
) == "configured")
|
||||
);
|
||||
} else {
|
||||
return []
|
||||
@@ -212,7 +212,7 @@
|
||||
inputMetrics.map((metric) => ({
|
||||
metric: metric,
|
||||
data: grouped.find((group) => group[0].name == metric),
|
||||
disabled: checkMetricDisabled(
|
||||
availability: checkMetricAvailability(
|
||||
globalMetrics,
|
||||
metric,
|
||||
thisJob.cluster,
|
||||
@@ -333,7 +333,28 @@
|
||||
{:else if thisJob && $jobMetrics?.data?.scopedJobStats}
|
||||
<!-- Note: Ignore '#snippet' Error in IDE -->
|
||||
{#snippet gridContent(item)}
|
||||
{#if item.data}
|
||||
{#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">
|
||||
<CardHeader class="mb-0">
|
||||
<b>Disabled Metric</b>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>No dataset(s) returned for <b>{item.metric}</b></p>
|
||||
<p class="mb-1">Metric has been disabled for subcluster <b>{thisJob.subCluster}</b>.</p>
|
||||
<p class="mb-1">To remove this card, open metric selection, de-select the metric, and press "Close and Apply".</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{:else if item?.data}
|
||||
<Metric
|
||||
bind:this={plots[item.metric]}
|
||||
job={thisJob}
|
||||
@@ -343,16 +364,6 @@
|
||||
presetScopes={item.data.map((x) => x.scope)}
|
||||
isShared={thisJob.shared != "none"}
|
||||
/>
|
||||
{:else if item.disabled == true}
|
||||
<Card color="info">
|
||||
<CardHeader class="mb-0">
|
||||
<b>Disabled Metric</b>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>Metric <b>{item.metric}</b> is disabled for cluster <b>{thisJob.cluster}:{thisJob.subCluster}</b>.</p>
|
||||
<p class="mb-1">To remove this card, open metric selection and press "Close and Apply".</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{:else}
|
||||
<Card color="warning" class="mt-2">
|
||||
<CardHeader class="mb-0">
|
||||
|
||||
@@ -142,6 +142,9 @@
|
||||
<Filters
|
||||
bind:this={filterComponent}
|
||||
{filterPresets}
|
||||
startTimeQuickSelect
|
||||
shortJobQuickSelect={(filterBuffer.length > 0)}
|
||||
shortJobCutoff={ccconfig?.jobList_hideShortRunningJobs}
|
||||
showFilter={!showCompare}
|
||||
matchedJobs={showCompare? matchedCompareJobs: matchedListJobs}
|
||||
applyFilters={(detail) => {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
} from "@urql/svelte";
|
||||
import {
|
||||
init,
|
||||
checkMetricDisabled,
|
||||
checkMetricAvailability,
|
||||
} from "./generic/utils.js";
|
||||
import PlotGrid from "./generic/PlotGrid.svelte";
|
||||
import MetricPlot from "./generic/plots/MetricPlot.svelte";
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
const filter = $derived([
|
||||
{ cluster: { eq: cluster } },
|
||||
{ node: { contains: hostname } },
|
||||
{ node: { eq: hostname } },
|
||||
{ state: ["running"] },
|
||||
]);
|
||||
|
||||
@@ -242,7 +242,27 @@
|
||||
{item.name}
|
||||
{systemUnits[item.name] ? "(" + systemUnits[item.name] + ")" : ""}
|
||||
</h4>
|
||||
{#if item.disabled === false && item.metric}
|
||||
{#if item.availability == "none"}
|
||||
<Card color="light" class="mx-2">
|
||||
<CardHeader class="mb-0">
|
||||
<b>Metric not configured</b>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>No datasets returned for <b>{item.name}</b>.</p>
|
||||
<p class="mb-1">Metric is not configured for cluster <b>{cluster}</b>.</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{:else if item.availability == "disabled"}
|
||||
<Card color="info" class="mx-2">
|
||||
<CardHeader class="mb-0">
|
||||
<b>Disabled Metric</b>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>No dataset(s) returned for <b>{item.name}</b></p>
|
||||
<p class="mb-1">Metric has been disabled for subcluster <b>{$nodeMetricsData.data.nodeMetrics[0].subCluster}</b>.</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{:else if item?.metric}
|
||||
<MetricPlot
|
||||
metric={item.name}
|
||||
timestep={item.metric.timestep}
|
||||
@@ -252,13 +272,6 @@
|
||||
enableFlip
|
||||
forNode
|
||||
/>
|
||||
{:else if item.disabled === true && item.metric}
|
||||
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="info"
|
||||
>Metric disabled for subcluster <code
|
||||
>{item.name}:{$nodeMetricsData.data.nodeMetrics[0]
|
||||
.subCluster}</code
|
||||
></Card
|
||||
>
|
||||
{:else}
|
||||
<Card color="warning" class="mx-2">
|
||||
<CardHeader class="mb-0">
|
||||
@@ -276,7 +289,7 @@
|
||||
items={$nodeMetricsData.data.nodeMetrics[0].metrics
|
||||
.map((m) => ({
|
||||
...m,
|
||||
disabled: checkMetricDisabled(
|
||||
availability: checkMetricAvailability(
|
||||
globalMetrics,
|
||||
m.name,
|
||||
cluster,
|
||||
|
||||
@@ -272,8 +272,8 @@
|
||||
<NodeOverview {cluster} {ccconfig} {selectedMetric} {globalMetrics} {from} {to} {hostnameFilter} {hoststateFilter}/>
|
||||
{:else}
|
||||
<!-- ROW2-2: Node List (Grid Included)-->
|
||||
<NodeList {cluster} {subCluster} {ccconfig} {globalMetrics}
|
||||
pendingSelectedMetrics={selectedMetrics} {selectedResolution} {hostnameFilter} {hoststateFilter} {from} {to} {systemUnits}/>
|
||||
<NodeList pendingSelectedMetrics={selectedMetrics} {cluster} {subCluster}
|
||||
{selectedResolution} {hostnameFilter} {hoststateFilter} {from} {to} {systemUnits}/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -219,9 +219,11 @@
|
||||
<Filters
|
||||
bind:this={filterComponent}
|
||||
{filterPresets}
|
||||
startTimeQuickSelect
|
||||
shortJobQuickSelect={(filterBuffer.length > 0)}
|
||||
shortJobCutoff={ccconfig?.jobList_hideShortRunningJobs}
|
||||
showFilter={!showCompare}
|
||||
matchedJobs={showCompare? matchedCompareJobs: matchedListJobs}
|
||||
startTimeQuickSelect
|
||||
applyFilters={(detail) => {
|
||||
jobFilters = [...detail.filters, { user: { eq: user.username } }];
|
||||
selectedCluster = jobFilters[0]?.cluster
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
- `filterPresets Object?`: Optional predefined filter values [Default: {}]
|
||||
- `disableClusterSelection Bool?`: Is the selection disabled [Default: false]
|
||||
- `startTimeQuickSelect Bool?`: Render startTime quick selections [Default: false]
|
||||
- `shortJobQuickSelect Bool?`: Render short job quick selections [Default: false]
|
||||
- `shortJobCutoff Int?`: Time in seconds for jobs to be considered short [Default: null]
|
||||
- `matchedJobs Number?`: Number of jobs matching the filter [Default: -2]
|
||||
- `showFilter Func`: If the filter component should be rendered in addition to total count info [Default: true]
|
||||
- `applyFilters Func`: The callback function to apply current filter selection
|
||||
@@ -25,6 +27,7 @@
|
||||
ButtonGroup,
|
||||
ButtonDropdown,
|
||||
Icon,
|
||||
Tooltip
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import Info from "./filters/InfoBox.svelte";
|
||||
import Cluster from "./filters/Cluster.svelte";
|
||||
@@ -36,6 +39,7 @@
|
||||
import Resources from "./filters/Resources.svelte";
|
||||
import Energy from "./filters/Energy.svelte";
|
||||
import Statistics from "./filters/Stats.svelte";
|
||||
import { formatDurationTime } from "./units.js";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
@@ -43,6 +47,8 @@
|
||||
filterPresets = {},
|
||||
disableClusterSelection = false,
|
||||
startTimeQuickSelect = false,
|
||||
shortJobQuickSelect = false,
|
||||
shortJobCutoff = 0,
|
||||
matchedJobs = -2,
|
||||
showFilter = true,
|
||||
applyFilters
|
||||
@@ -335,6 +341,44 @@
|
||||
<DropdownItem onclick={() => (isStatsOpen = true)}>
|
||||
<Icon name="bar-chart" onclick={() => (isStatsOpen = true)} /> Statistics
|
||||
</DropdownItem>
|
||||
{#if shortJobQuickSelect && shortJobCutoff > 0}
|
||||
<DropdownItem divider />
|
||||
<DropdownItem header>
|
||||
Short Jobs Selection
|
||||
<Icon id="shortjobsfilter-info" style="cursor:help; padding-right: 8px;" size="sm" name="info-circle"/>
|
||||
<Tooltip target={`shortjobsfilter-info`} placement="right">
|
||||
Job duration less than {formatDurationTime(shortJobCutoff)}
|
||||
</Tooltip>
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onclick={() => {
|
||||
filters.duration = {
|
||||
moreThan: null,
|
||||
lessThan: shortJobCutoff,
|
||||
from: null,
|
||||
to: null
|
||||
}
|
||||
updateFilters();
|
||||
}}
|
||||
>
|
||||
<Icon name="stopwatch" />
|
||||
Only Short Jobs
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onclick={() => {
|
||||
filters.duration = {
|
||||
moreThan: shortJobCutoff,
|
||||
lessThan: null,
|
||||
from: null,
|
||||
to: null
|
||||
}
|
||||
updateFilters();
|
||||
}}
|
||||
>
|
||||
<Icon name="stopwatch" />
|
||||
Exclude Short Jobs
|
||||
</DropdownItem>
|
||||
{/if}
|
||||
{#if startTimeQuickSelect}
|
||||
<DropdownItem divider />
|
||||
<DropdownItem header>Start Time Quick Selection</DropdownItem>
|
||||
@@ -407,7 +451,7 @@
|
||||
|
||||
{#if filters.startTime.range}
|
||||
<Info icon="calendar-range" onclick={() => (isStartTimeOpen = true)}>
|
||||
{startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel }
|
||||
Job Start: {startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel }
|
||||
</Info>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -112,11 +112,7 @@
|
||||
// (Re-)query and optionally set new filters; Query will be started reactively.
|
||||
export function queryJobs(filters) {
|
||||
if (filters != null) {
|
||||
let minRunningFor = ccconfig.jobList_hideShortRunningJobs;
|
||||
if (minRunningFor && minRunningFor > 0) {
|
||||
filters.push({ minRunningFor });
|
||||
}
|
||||
filter = filters;
|
||||
filter = [...filters];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,10 +180,6 @@
|
||||
// (Re-)query and optionally set new filters; Query will be started reactively.
|
||||
export function queryJobs(filters) {
|
||||
if (filters != null) {
|
||||
let minRunningFor = ccconfig.jobList_hideShortRunningJobs;
|
||||
if (minRunningFor && minRunningFor > 0) {
|
||||
filters.push({ minRunningFor });
|
||||
}
|
||||
filter = [...filters];
|
||||
}
|
||||
};
|
||||
@@ -309,7 +305,7 @@
|
||||
{#if $jobsStore.fetching || !$jobsStore.data}
|
||||
<tr>
|
||||
<td colspan={metrics.length + 1}>
|
||||
<div style="text-align:center;">
|
||||
<div style="text-align:center; margin-top: 1rem;">
|
||||
<Spinner secondary />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<script module>
|
||||
export const startTimeSelectOptions = [
|
||||
{ range: "", rangeLabel: "No Selection"},
|
||||
{ range: "last6h", rangeLabel: "Last 6hrs"},
|
||||
{ range: "last24h", rangeLabel: "Last 24hrs"},
|
||||
{ range: "last6h", rangeLabel: "Last 6 hrs"},
|
||||
{ range: "last24h", rangeLabel: "Last 24 hrs"},
|
||||
{ range: "last7d", rangeLabel: "Last 7 days"},
|
||||
{ range: "last30d", rangeLabel: "Last 30 days"}
|
||||
];
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<script>
|
||||
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
||||
import { Card, Spinner } from "@sveltestrap/sveltestrap";
|
||||
import { maxScope, checkMetricDisabled } from "../utils.js";
|
||||
import { maxScope, checkMetricAvailability } from "../utils.js";
|
||||
import JobInfo from "./JobInfo.svelte";
|
||||
import MetricPlot from "../plots/MetricPlot.svelte";
|
||||
import JobFootprint from "../helper/JobFootprint.svelte";
|
||||
@@ -99,7 +99,7 @@
|
||||
})
|
||||
);
|
||||
|
||||
const refinedData = $derived($metricsQuery?.data?.jobMetrics ? sortAndSelectScope($metricsQuery.data.jobMetrics) : []);
|
||||
const refinedData = $derived($metricsQuery?.data?.jobMetrics ? sortAndSelectScope(metrics, $metricsQuery.data.jobMetrics) : []);
|
||||
|
||||
/* Effects */
|
||||
$effect(() => {
|
||||
@@ -140,6 +140,26 @@
|
||||
});
|
||||
}
|
||||
|
||||
function sortAndSelectScope(metricList = [], jobMetrics = []) {
|
||||
const pendingData = [];
|
||||
metricList.forEach((metricName) => {
|
||||
const pendingMetric = {
|
||||
name: metricName,
|
||||
availability: checkMetricAvailability(
|
||||
globalMetrics,
|
||||
metricName,
|
||||
job.cluster,
|
||||
job.subCluster,
|
||||
),
|
||||
data: null
|
||||
};
|
||||
const scopesData = jobMetrics.filter((jobMetric) => jobMetric.name == metricName)
|
||||
if (scopesData.length > 0) pendingMetric.data = selectScope(scopesData)
|
||||
pendingData.push(pendingMetric)
|
||||
});
|
||||
return pendingData;
|
||||
};
|
||||
|
||||
const selectScope = (jobMetrics) =>
|
||||
jobMetrics.reduce(
|
||||
(a, b) =>
|
||||
@@ -152,30 +172,6 @@
|
||||
: a,
|
||||
jobMetrics[0],
|
||||
);
|
||||
|
||||
const sortAndSelectScope = (jobMetrics) =>
|
||||
metrics
|
||||
.map((name) => jobMetrics.filter((jobMetric) => jobMetric.name == name))
|
||||
.map((jobMetrics) => ({
|
||||
disabled: false,
|
||||
data: jobMetrics.length > 0 ? selectScope(jobMetrics) : null,
|
||||
}))
|
||||
.map((jobMetric) => {
|
||||
if (jobMetric.data) {
|
||||
return {
|
||||
name: jobMetric.data.name,
|
||||
disabled: checkMetricDisabled(
|
||||
globalMetrics,
|
||||
jobMetric.data.name,
|
||||
job.cluster,
|
||||
job.subCluster,
|
||||
),
|
||||
data: jobMetric.data,
|
||||
};
|
||||
} else {
|
||||
return jobMetric;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<tr>
|
||||
@@ -211,39 +207,41 @@
|
||||
{/if}
|
||||
{#each refinedData as metric, i (metric?.name || i)}
|
||||
<td>
|
||||
{#key metric}
|
||||
{#if metric?.data}
|
||||
{#if metric?.disabled}
|
||||
<Card body class="mx-2" color="info">
|
||||
Metric <b>{metric.data.name}</b>: Disabled for subcluster <code>{job.subCluster}</code>
|
||||
</Card>
|
||||
{:else}
|
||||
<MetricPlot
|
||||
onZoom={(detail) => handleZoom(detail, metric.data.name)}
|
||||
height={plotHeight}
|
||||
timestep={metric.data.metric.timestep}
|
||||
scope={metric.data.scope}
|
||||
series={metric.data.metric.series}
|
||||
statisticsSeries={metric.data.metric.statisticsSeries}
|
||||
metric={metric.data.name}
|
||||
cluster={clusterInfos.find((c) => c.name == job.cluster)}
|
||||
subCluster={job.subCluster}
|
||||
isShared={job.shared != "none"}
|
||||
numhwthreads={job.numHWThreads}
|
||||
numaccs={job.numAcc}
|
||||
zoomState={zoomStates[metric.data.name] || null}
|
||||
thresholdState={thresholdStates[metric.data.name] || null}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<Card body class="mx-2" color="warning">
|
||||
<p>No dataset(s) returned for <b>{metrics[i]}</b></p>
|
||||
<p class="mb-1">Metric or host was not found in metric store for cluster <b>{job.cluster}</b>:</p>
|
||||
<p class="mb-1">Identical messages in <i>{metrics[i]} column</i>: Metric not found.</p>
|
||||
<p class="mb-1">Identical messages in <i>job {job.jobId} row</i>: Host not found.</p>
|
||||
</Card>
|
||||
{/if}
|
||||
{/key}
|
||||
{#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">
|
||||
<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>
|
||||
</Card>
|
||||
{:else if metric?.data}
|
||||
<MetricPlot
|
||||
onZoom={(detail) => handleZoom(detail, metric.data.name)}
|
||||
height={plotHeight}
|
||||
timestep={metric.data.metric.timestep}
|
||||
scope={metric.data.scope}
|
||||
series={metric.data.metric.series}
|
||||
statisticsSeries={metric.data.metric.statisticsSeries}
|
||||
metric={metric.data.name}
|
||||
cluster={clusterInfos.find((c) => c.name == job.cluster)}
|
||||
subCluster={job.subCluster}
|
||||
isShared={job.shared != "none"}
|
||||
numhwthreads={job.numHWThreads}
|
||||
numaccs={job.numAcc}
|
||||
zoomState={zoomStates[metric.data.name] || null}
|
||||
thresholdState={thresholdStates[metric.data.name] || null}
|
||||
/>
|
||||
{:else}
|
||||
<Card body class="mx-2" color="warning">
|
||||
<p>No dataset(s) returned for <b>{metrics[i]}</b></p>
|
||||
<p class="mb-1">Metric or host was not found in metric store for cluster <b>{job.cluster}</b>:</p>
|
||||
<p class="mb-1">Identical messages in <i>{metrics[i]} column</i>: Metric not found.</p>
|
||||
<p class="mb-1">Identical messages in <i>job {job.jobId} row</i>: Host not found.</p>
|
||||
</Card>
|
||||
{/if}
|
||||
</td>
|
||||
{:else}
|
||||
<td>
|
||||
|
||||
@@ -88,16 +88,19 @@
|
||||
|
||||
function printAvailability(metric, cluster) {
|
||||
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
|
||||
if (!cluster) {
|
||||
return avail.map((av) => av.cluster).join(', ')
|
||||
} else {
|
||||
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
|
||||
if (subAvail) {
|
||||
return subAvail.join(', ')
|
||||
if (avail) {
|
||||
if (!cluster) {
|
||||
return avail.map((av) => av.cluster).join(', ')
|
||||
} else {
|
||||
return `Not available for ${cluster}`
|
||||
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
|
||||
if (subAvail) {
|
||||
return subAvail.join(', ')
|
||||
} else {
|
||||
return `Not available for ${cluster}`
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function columnsDragOver(event) {
|
||||
|
||||
@@ -302,20 +302,36 @@ export function stickyHeader(datatableHeaderSelector, updatePading) {
|
||||
onDestroy(() => document.removeEventListener("scroll", onscroll));
|
||||
}
|
||||
|
||||
export function checkMetricDisabled(gm, 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)
|
||||
// Return inverse logic
|
||||
return !available
|
||||
export function checkMetricAvailability(gms, m, c, s = "") { // [g]lobal[m]etrics, [m]etric, [c]luster, [s]ubcluster
|
||||
let pendingAvailability = "none"
|
||||
const configured = gms?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)
|
||||
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
|
||||
let result = {};
|
||||
ma.forEach((m) => {
|
||||
// Return named inverse logic: !available
|
||||
result[m] = !(gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s))
|
||||
});
|
||||
return result
|
||||
}
|
||||
// export function checkMetricDisabled(gm, 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)
|
||||
// // Return inverse logic
|
||||
// return !available
|
||||
// }
|
||||
|
||||
// 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 = []) {
|
||||
// console.time('stats')
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
/* Const Init */
|
||||
const ccconfig = getContext("cc-config");
|
||||
const globalMetrics = getContext("globalMetrics");
|
||||
const client = getContextClient();
|
||||
|
||||
/* State Init */
|
||||
@@ -139,6 +140,7 @@
|
||||
|
||||
<HistogramSelection
|
||||
{cluster}
|
||||
{globalMetrics}
|
||||
bind:isOpen={isHistogramSelectionOpen}
|
||||
presetSelectedHistograms={selectedHistograms}
|
||||
configName="statusView_selectedHistograms"
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `subCluster String`: The nodes' subCluster [Default: ""]
|
||||
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
||||
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
|
||||
- `pendingSelectedMetrics [String]`: The array of selected metrics [Default []]
|
||||
- `selectedResolution Number?`: The selected data resolution [Default: 0]
|
||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
||||
@@ -16,7 +14,7 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { untrack } from "svelte";
|
||||
import { untrack, getContext } from "svelte";
|
||||
import { queryStore, gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||
import { Row, Col, Card, Table, Spinner } from "@sveltestrap/sveltestrap";
|
||||
import { stickyHeader } from "../generic/utils.js";
|
||||
@@ -27,8 +25,6 @@
|
||||
let {
|
||||
cluster,
|
||||
subCluster = "",
|
||||
ccconfig = null,
|
||||
globalMetrics = null,
|
||||
pendingSelectedMetrics = [],
|
||||
selectedResolution = 0,
|
||||
hostnameFilter = "",
|
||||
@@ -99,11 +95,16 @@
|
||||
let headerPaddingTop = $state(0);
|
||||
|
||||
/* Derived */
|
||||
const initialized = $derived(getContext("initialized") || false);
|
||||
const ccconfig = $derived(initialized ? getContext("cc-config") : null);
|
||||
const globalMetrics = $derived(initialized ? getContext("globalMetrics") : null);
|
||||
const usePaging = $derived(ccconfig ? ccconfig.nodeList_usePaging : false);
|
||||
|
||||
let selectedMetrics = $derived(pendingSelectedMetrics);
|
||||
let itemsPerPage = $derived(usePaging ? (ccconfig?.nodeList_nodesPerPage || 10) : 10);
|
||||
const usePaging = $derived(ccconfig?.nodeList_usePaging || false);
|
||||
const paging = $derived({ itemsPerPage, page });
|
||||
const nodesQuery = $derived(queryStore({
|
||||
let paging = $derived({ itemsPerPage, page });
|
||||
|
||||
const nodesStore = $derived(queryStore({
|
||||
client: client,
|
||||
query: nodeListQuery,
|
||||
variables: {
|
||||
@@ -121,8 +122,8 @@
|
||||
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 */
|
||||
$effect(() => {
|
||||
if (!usePaging) {
|
||||
@@ -134,7 +135,7 @@
|
||||
} = document.documentElement;
|
||||
|
||||
// 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
|
||||
};
|
||||
});
|
||||
@@ -142,21 +143,30 @@
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if ($nodesQuery?.data) {
|
||||
if ($nodesStore?.data) {
|
||||
untrack(() => {
|
||||
handleNodes($nodesQuery?.data?.nodeMetricsList?.items);
|
||||
handleNodes($nodesStore?.data?.nodeMetricsList?.items);
|
||||
});
|
||||
selectedMetrics = [...pendingSelectedMetrics]; // Trigger Rerender in NodeListRow Only After Data is Fetched
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// Triggers (Except Paging)
|
||||
// Update NodeListRows metrics only: Keep ordered nodes on page 1
|
||||
from, to
|
||||
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
|
||||
// Continous Scroll: Paging if parameters change: Existing entries will not match new selections
|
||||
// Nodes Array Reset in HandleNodes func
|
||||
nodes = [];
|
||||
if (!usePaging) {
|
||||
page = 1;
|
||||
}
|
||||
@@ -227,7 +237,7 @@
|
||||
style="padding-top: {headerPaddingTop}px;"
|
||||
>
|
||||
{cluster} Node Info
|
||||
{#if $nodesQuery.fetching}
|
||||
{#if $nodesStore.fetching}
|
||||
<Spinner size="sm" style="margin-left:10px;" secondary />
|
||||
{/if}
|
||||
</th>
|
||||
@@ -244,22 +254,24 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if $nodesQuery.error}
|
||||
{#if $nodesStore.error}
|
||||
<Row>
|
||||
<Col>
|
||||
<Card body color="danger">{$nodesQuery.error.message}</Card>
|
||||
<Card body color="danger">{$nodesStore.error.message}</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
{:else}
|
||||
{#each nodes as nodeData (nodeData.host)}
|
||||
<NodeListRow {nodeData} {cluster} {selectedMetrics} {globalMetrics}/>
|
||||
<NodeListRow {nodeData} {cluster} {selectedMetrics} {globalMetrics} nodeDataFetching={$nodesStore.fetching}/>
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan={selectedMetrics.length + 1}> No nodes found </td>
|
||||
</tr>
|
||||
{#if !$nodesStore.fetching}
|
||||
<tr>
|
||||
<td colspan={selectedMetrics.length + 1}> No nodes found </td>
|
||||
</tr>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#if $nodesQuery.fetching || !$nodesQuery.data}
|
||||
{#if $nodesStore.fetching || !$nodesStore.data}
|
||||
<tr>
|
||||
<td colspan={pendingSelectedMetrics.length + 1}>
|
||||
<div style="text-align:center;">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script>
|
||||
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
||||
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";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
@@ -87,6 +87,7 @@
|
||||
},
|
||||
}));
|
||||
|
||||
const notConfigured = $derived(checkMetricAvailability(globalMetrics, selectedMetric, cluster) == "none");
|
||||
const mappedData = $derived(handleQueryData($nodesQuery?.data));
|
||||
const filteredData = $derived(mappedData.filter((h) => {
|
||||
if (hostnameFilter) {
|
||||
@@ -110,7 +111,7 @@
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
let pendingMapped = [];
|
||||
if (rawData.length > 0) {
|
||||
pendingMapped = rawData.map((h) => ({
|
||||
@@ -120,12 +121,11 @@
|
||||
data: h.metrics.filter(
|
||||
(m) => m?.name == selectedMetric && m.scope == "node",
|
||||
),
|
||||
// TODO: Move To New Func Variant With Disabled Check on WHole Cluster Level: This never Triggers!
|
||||
disabled: checkMetricDisabled(globalMetrics, selectedMetric, cluster, h.subCluster),
|
||||
availability: checkMetricAvailability(globalMetrics, selectedMetric, cluster, h.subCluster),
|
||||
}))
|
||||
.sort((a, b) => a.host.localeCompare(b.host))
|
||||
}
|
||||
|
||||
|
||||
return pendingMapped;
|
||||
}
|
||||
</script>
|
||||
@@ -162,35 +162,32 @@
|
||||
</Badge>
|
||||
</span>
|
||||
</div>
|
||||
{#if item?.data}
|
||||
{#if item.disabled === true}
|
||||
<!-- TODO: Will never be Shown: Overview Single Metric Return Will be Null, see Else Case-->
|
||||
<Card body class="mx-3" color="info"
|
||||
>Metric disabled for subcluster <code
|
||||
>{selectedMetric}:{item.subCluster}</code
|
||||
></Card
|
||||
>
|
||||
{:else if item.disabled === false}
|
||||
<!-- "No Data"-Warning included in MetricPlot-Component -->
|
||||
<!-- #key: X-axis keeps last selected timerange otherwise -->
|
||||
{#key item.data[0].metric.series[0].data.length}
|
||||
<MetricPlot
|
||||
timestep={item.data[0].metric.timestep}
|
||||
series={item.data[0].metric.series}
|
||||
metric={item.data[0].name}
|
||||
{cluster}
|
||||
subCluster={item.subCluster}
|
||||
forNode
|
||||
enableFlip
|
||||
/>
|
||||
{/key}
|
||||
{:else}
|
||||
<Card body class="mx-3" color="info">
|
||||
Global Metric List Not Initialized
|
||||
Can not determine {selectedMetric} availability: Please Reload Page
|
||||
</Card>
|
||||
{/if}
|
||||
{#if item?.availability == "disabled"}
|
||||
<Card color="info">
|
||||
<CardHeader class="mb-0">
|
||||
<b>Disabled Metric</b>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>No dataset(s) returned for <b>{selectedMetric}</b></p>
|
||||
<p class="mb-1">Metric has been disabled for subcluster <b>{item.subCluster}</b>.</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{:else if item?.data}
|
||||
<!-- "Empty Series"-Warning included in MetricPlot-Component -->
|
||||
<!-- #key: X-axis keeps last selected timerange otherwise -->
|
||||
{#key item.data[0].metric.series[0].data.length}
|
||||
<MetricPlot
|
||||
timestep={item.data[0].metric.timestep}
|
||||
series={item.data[0].metric.series}
|
||||
metric={item.data[0].name}
|
||||
{cluster}
|
||||
subCluster={item.subCluster}
|
||||
forNode
|
||||
enableFlip
|
||||
/>
|
||||
{/key}
|
||||
{:else}
|
||||
<!-- Should Not Appear -->
|
||||
<Card color="warning">
|
||||
<CardHeader class="mb-0">
|
||||
<b>Missing Metric</b>
|
||||
@@ -205,10 +202,34 @@
|
||||
{/each}
|
||||
{/key}
|
||||
</Row>
|
||||
{:else if hostnameFilter || hoststateFilter != 'all'}
|
||||
<Row class="mx-1">
|
||||
<Card class="px-0">
|
||||
<CardHeader>
|
||||
<b>Empty Filter Return</b>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>No datasets returned for <b>{selectedMetric}</b>.</p>
|
||||
<p class="mb-1">Hostname filter and/or host state filter returned no matches.</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</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}
|
||||
<Row>
|
||||
<Card color="warning">
|
||||
<CardHeader class="mb-0">
|
||||
<Row class="mx-1">
|
||||
<Card class="px-0" color="warning">
|
||||
<CardHeader>
|
||||
<b>Missing Metric</b>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
|
||||
/* 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));
|
||||
@@ -84,10 +86,17 @@
|
||||
<Row cols={{xs: 1, lg: 2}}>
|
||||
<Col class="mb-2 mb-lg-0">
|
||||
<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">
|
||||
<Icon name="exclamation-circle" style="padding-right: 0.5rem;"/>
|
||||
<span>Jobs</span>
|
||||
<span>Info</span>
|
||||
</InputGroupText>
|
||||
<Button class="flex-grow-1" color="danger" disabled>
|
||||
No Metrics
|
||||
@@ -95,7 +104,7 @@
|
||||
{:else if metricWarn}
|
||||
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
|
||||
<Icon name="info-circle" style="padding-right: 0.5rem;"/>
|
||||
<span>Jobs</span>
|
||||
<span>Info</span>
|
||||
</InputGroupText>
|
||||
<Button class="flex-grow-1" color="warning" disabled>
|
||||
Missing Metric
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `nodeData Object`: The node data object including metric data
|
||||
- `nodeDataFetching Bool`: Whether the metric query still runs
|
||||
- `selectedMetrics [String]`: The array of selected metrics
|
||||
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
|
||||
-->
|
||||
@@ -16,7 +17,7 @@
|
||||
} from "@urql/svelte";
|
||||
import uPlot from "uplot";
|
||||
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 NodeInfo from "./NodeInfo.svelte";
|
||||
|
||||
@@ -24,6 +25,7 @@
|
||||
let {
|
||||
cluster,
|
||||
nodeData,
|
||||
nodeDataFetching,
|
||||
selectedMetrics,
|
||||
globalMetrics
|
||||
} = $props();
|
||||
@@ -72,10 +74,30 @@
|
||||
);
|
||||
|
||||
const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null);
|
||||
const refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(nodeData.metrics) : []);
|
||||
const dataHealth = $derived(refinedData.filter((rd) => rd.disabled === false).map((enabled) => (enabled?.data?.metric?.series?.length > 0)));
|
||||
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 = []) {
|
||||
const pendingData = [];
|
||||
metricList.forEach((metricName) => {
|
||||
const pendingMetric = {
|
||||
name: metricName,
|
||||
availability: checkMetricAvailability(
|
||||
globalMetrics,
|
||||
metricName,
|
||||
cluster,
|
||||
nodeData.subCluster,
|
||||
),
|
||||
data: null
|
||||
};
|
||||
const scopesData = nodeMetrics.filter((nodeMetric) => nodeMetric.name == metricName)
|
||||
if (scopesData.length > 0) pendingMetric.data = selectScope(scopesData)
|
||||
pendingData.push(pendingMetric)
|
||||
});
|
||||
return pendingData;
|
||||
};
|
||||
|
||||
const selectScope = (nodeMetrics) =>
|
||||
nodeMetrics.reduce(
|
||||
(a, b) =>
|
||||
@@ -83,29 +105,6 @@
|
||||
nodeMetrics[0],
|
||||
);
|
||||
|
||||
const sortAndSelectScope = (allNodeMetrics) =>
|
||||
selectedMetrics
|
||||
.map((selectedName) => allNodeMetrics.filter((nodeMetric) => nodeMetric.name == selectedName))
|
||||
.map((matchedNodeMetrics) => ({
|
||||
disabled: false,
|
||||
data: matchedNodeMetrics.length > 0 ? selectScope(matchedNodeMetrics) : null,
|
||||
}))
|
||||
.map((scopedNodeMetric) => {
|
||||
if (scopedNodeMetric?.data) {
|
||||
return {
|
||||
disabled: checkMetricDisabled(
|
||||
globalMetrics,
|
||||
scopedNodeMetric.data.name,
|
||||
cluster,
|
||||
nodeData.subCluster,
|
||||
),
|
||||
data: scopedNodeMetric.data,
|
||||
};
|
||||
} else {
|
||||
return scopedNodeMetric;
|
||||
}
|
||||
});
|
||||
|
||||
function buildExtendedLegend() {
|
||||
let pendingExtendedLegendData = null
|
||||
// Build Extended for allocated nodes [Commented: Only Build extended Legend For Shared Nodes]
|
||||
@@ -133,23 +132,6 @@
|
||||
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>
|
||||
|
||||
<tr>
|
||||
@@ -173,64 +155,64 @@
|
||||
{#each refinedData as metricData, i (metricData?.data?.name || i)}
|
||||
{#key metricData}
|
||||
<td>
|
||||
{#if metricData?.disabled}
|
||||
<Card body class="mx-2" color="info"
|
||||
>Metric <b>{selectedMetrics[i]}</b> disabled for subcluster <code
|
||||
>{nodeData.subCluster}</code
|
||||
></Card
|
||||
>
|
||||
{#if !metricData?.data && nodeDataFetching}
|
||||
<div style="text-align:center; margin-top: 1rem;">
|
||||
<Spinner secondary />
|
||||
</div>
|
||||
{:else 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">
|
||||
<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>
|
||||
</Card>
|
||||
{:else if !metricData?.data}
|
||||
<Card body class="mx-2" color="warning">
|
||||
<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>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
|
||||
<p class="mb-1">Metric or host was not found in metric store for cluster <b>{cluster}</b>.</p>
|
||||
</Card>
|
||||
{:else if !metricData?.data?.name}
|
||||
<Card body class="mx-2" color="warning"
|
||||
>Metric without name for subcluster <code
|
||||
>{`Metric Index ${i}`}:{nodeData.subCluster}</code
|
||||
></Card
|
||||
>
|
||||
{:else if !!metricData.data?.metric.statisticsSeries}
|
||||
<!-- "No Data"-Warning included in MetricPlot-Component -->
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
subCluster={nodeData.subCluster}
|
||||
metric={metricData.data.name}
|
||||
scope={metricData.data.scope}
|
||||
timestep={metricData.data.metric.timestep}
|
||||
series={metricData.data.metric.series}
|
||||
statisticsSeries={metricData.data?.metric.statisticsSeries}
|
||||
useStatsSeries={!!metricData.data?.metric.statisticsSeries}
|
||||
height={175}
|
||||
{plotSync}
|
||||
forNode
|
||||
/>
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
subCluster={nodeData.subCluster}
|
||||
metric={metricData.data.name}
|
||||
scope={metricData.data.scope}
|
||||
timestep={metricData.data.metric.timestep}
|
||||
series={metricData.data.metric.series}
|
||||
statisticsSeries={metricData.data?.metric.statisticsSeries}
|
||||
useStatsSeries={!!metricData.data?.metric.statisticsSeries}
|
||||
height={175}
|
||||
{plotSync}
|
||||
forNode
|
||||
/>
|
||||
<div class="my-2"></div>
|
||||
{#key extendedLegendData}
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
subCluster={nodeData.subCluster}
|
||||
metric={metricData.data.name}
|
||||
scope={metricData.data.scope}
|
||||
timestep={metricData.data.metric.timestep}
|
||||
series={metricData.data.metric.series}
|
||||
height={175}
|
||||
{extendedLegendData}
|
||||
{plotSync}
|
||||
forNode
|
||||
/>
|
||||
{/key}
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
subCluster={nodeData.subCluster}
|
||||
metric={metricData.data.name}
|
||||
scope={metricData.data.scope}
|
||||
timestep={metricData.data.metric.timestep}
|
||||
series={metricData.data.metric.series}
|
||||
height={175}
|
||||
{extendedLegendData}
|
||||
{plotSync}
|
||||
forNode
|
||||
/>
|
||||
{:else}
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
subCluster={nodeData.subCluster}
|
||||
metric={metricData.data.name}
|
||||
scope={metricData.data.scope}
|
||||
timestep={metricData.data.metric.timestep}
|
||||
series={metricData.data.metric.series}
|
||||
height={375}
|
||||
forNode
|
||||
/>
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
subCluster={nodeData.subCluster}
|
||||
metric={metricData.data.name}
|
||||
scope={metricData.data.scope}
|
||||
timestep={metricData.data.metric.timestep}
|
||||
series={metricData.data.metric.series}
|
||||
height={375}
|
||||
forNode
|
||||
/>
|
||||
{/if}
|
||||
</td>
|
||||
{/key}
|
||||
|
||||
Reference in New Issue
Block a user