Merge pull request #347 from ClusterCockpit/dev

Dev
This commit is contained in:
Jan Eitzinger 2025-03-03 11:34:54 +01:00 committed by GitHub
commit acfa3baeb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 204 additions and 115 deletions

View File

@ -16,7 +16,7 @@ type DefaultMetricsConfig struct {
} }
func LoadDefaultMetricsConfig() (*DefaultMetricsConfig, error) { func LoadDefaultMetricsConfig() (*DefaultMetricsConfig, error) {
filePath := "configs/default_metrics.json" filePath := "default_metrics.json"
if _, err := os.Stat(filePath); os.IsNotExist(err) { if _, err := os.Stat(filePath); os.IsNotExist(err) {
return nil, nil return nil, nil
} }

View File

@ -20,6 +20,7 @@
Card, Card,
Table, Table,
Icon, Icon,
Tooltip
} from "@sveltestrap/sveltestrap"; } from "@sveltestrap/sveltestrap";
import { import {
init, init,
@ -70,6 +71,8 @@
...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]), ...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]),
]; ];
$: clusterName = cluster?.name ? cluster.name : cluster;
const sortOptions = [ const sortOptions = [
{ key: "totalWalltime", label: "Walltime" }, { key: "totalWalltime", label: "Walltime" },
{ key: "totalNodeHours", label: "Node Hours" }, { key: "totalNodeHours", label: "Node Hours" },
@ -159,6 +162,7 @@
groupBy: $groupBy groupBy: $groupBy
) { ) {
id id
name
totalWalltime totalWalltime
totalNodeHours totalNodeHours
totalCoreHours totalCoreHours
@ -422,15 +426,22 @@
<tr> <tr>
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td> <td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
{#if groupSelection.key == "user"} {#if groupSelection.key == "user"}
<th scope="col" <th scope="col" id="topName-{te.id}"
><a href="/monitoring/user/{te.id}?cluster={cluster}" ><a href="/monitoring/user/{te.id}?cluster={clusterName}"
>{te.id}</a >{te.id}</a
></th ></th
> >
{#if te?.name}
<Tooltip
target={`topName-${te.id}`}
placement="left"
>{te.name}</Tooltip
>
{/if}
{:else} {:else}
<th scope="col" <th scope="col"
><a ><a
href="/monitoring/jobs/?cluster={cluster}&project={te.id}&projectMatch=eq" href="/monitoring/jobs/?cluster={clusterName}&project={te.id}&projectMatch=eq"
>{te.id}</a >{te.id}</a
></th ></th
> >

View File

@ -58,7 +58,8 @@
let plots = {}, let plots = {},
statsTable statsTable
let missingMetrics = [], let availableMetrics = new Set(),
missingMetrics = [],
missingHosts = [], missingHosts = [],
somethingMissing = false; somethingMissing = false;
@ -128,7 +129,12 @@
const pendingMetrics = [ const pendingMetrics = [
...(ccconfig[`job_view_selectedMetrics:${job.cluster}`] || ...(ccconfig[`job_view_selectedMetrics:${job.cluster}`] ||
ccconfig[`job_view_selectedMetrics`] $initq.data.globalMetrics.reduce((names, gm) => {
if (gm.availability.find((av) => av.cluster === job.cluster)) {
names.push(gm.name);
}
return names;
}, [])
), ),
...(ccconfig[`job_view_nodestats_selectedMetrics:${job.cluster}`] || ...(ccconfig[`job_view_nodestats_selectedMetrics:${job.cluster}`] ||
ccconfig[`job_view_nodestats_selectedMetrics`] ccconfig[`job_view_nodestats_selectedMetrics`]
@ -293,7 +299,7 @@
{#if $initq.data} {#if $initq.data}
<Col xs="auto"> <Col xs="auto">
<Button outline on:click={() => (isMetricsSelectionOpen = true)} color="primary"> <Button outline on:click={() => (isMetricsSelectionOpen = true)} color="primary">
Select Metrics Select Metrics (Selected {selectedMetrics.length} of {availableMetrics.size} available)
</Button> </Button>
</Col> </Col>
{/if} {/if}
@ -431,6 +437,7 @@
configName="job_view_selectedMetrics" configName="job_view_selectedMetrics"
bind:metrics={selectedMetrics} bind:metrics={selectedMetrics}
bind:isOpen={isMetricsSelectionOpen} bind:isOpen={isMetricsSelectionOpen}
bind:allMetrics={availableMetrics}
/> />
{/if} {/if}

View File

@ -19,6 +19,7 @@
Progress, Progress,
Icon, Icon,
Button, Button,
Tooltip
} from "@sveltestrap/sveltestrap"; } from "@sveltestrap/sveltestrap";
import { import {
queryStore, queryStore,
@ -75,9 +76,9 @@
); );
let isHistogramSelectionOpen = false; let isHistogramSelectionOpen = false;
$: metricsInHistograms = cluster $: selectedHistograms = cluster
? ccconfig[`user_view_histogramMetrics:${cluster}`] || [] ? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
: ccconfig.user_view_histogramMetrics || []; : ccconfig['user_view_histogramMetrics'] || [];
const client = getContextClient(); const client = getContextClient();
// Note: nodeMetrics are requested on configured $timestep resolution // Note: nodeMetrics are requested on configured $timestep resolution
@ -90,7 +91,7 @@
$metrics: [String!] $metrics: [String!]
$from: Time! $from: Time!
$to: Time! $to: Time!
$metricsInHistograms: [String!] $selectedHistograms: [String!]
) { ) {
nodeMetrics( nodeMetrics(
cluster: $cluster cluster: $cluster
@ -116,7 +117,7 @@
} }
} }
stats: jobsStatistics(filter: $filter, metrics: $metricsInHistograms) { stats: jobsStatistics(filter: $filter, metrics: $selectedHistograms) {
histDuration { histDuration {
count count
value value
@ -157,7 +158,7 @@
from: from.toISOString(), from: from.toISOString(),
to: to.toISOString(), to: to.toISOString(),
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }], filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
metricsInHistograms: metricsInHistograms, selectedHistograms: selectedHistograms,
}, },
}); });
@ -177,6 +178,7 @@
groupBy: USER groupBy: USER
) { ) {
id id
name
totalJobs totalJobs
totalNodes totalNodes
totalCores totalCores
@ -515,12 +517,19 @@
{#each $topUserQuery.data.topUser as tu, i} {#each $topUserQuery.data.topUser as tu, i}
<tr> <tr>
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td> <td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
<th scope="col" <th scope="col" id="topName-{tu.id}"
><a ><a
href="/monitoring/user/{tu.id}?cluster={cluster}&state=running" href="/monitoring/user/{tu.id}?cluster={cluster}&state=running"
>{tu.id}</a >{tu.id}</a
></th ></th
> >
{#if tu?.name}
<Tooltip
target={`topName-${tu.id}`}
placement="left"
>{tu.name}</Tooltip
>
{/if}
<td>{tu[topUserSelection.key]}</td> <td>{tu[topUserSelection.key]}</td>
</tr> </tr>
{/each} {/each}
@ -652,7 +661,7 @@
<!-- Selectable Stats as Histograms : Average Values of Running Jobs --> <!-- Selectable Stats as Histograms : Average Values of Running Jobs -->
{#if metricsInHistograms} {#if selectedHistograms}
{#key $mainQuery.data.stats[0].histMetrics} {#key $mainQuery.data.stats[0].histMetrics}
<PlotGrid <PlotGrid
let:item let:item
@ -675,6 +684,6 @@
<HistogramSelection <HistogramSelection
bind:cluster bind:cluster
bind:metricsInHistograms bind:selectedHistograms
bind:isOpen={isHistogramSelectionOpen} bind:isOpen={isHistogramSelectionOpen}
/> />

View File

@ -68,16 +68,16 @@
let durationBinOptions = ["1m","10m","1h","6h","12h"]; let durationBinOptions = ["1m","10m","1h","6h","12h"];
let metricBinOptions = [10, 20, 50, 100]; let metricBinOptions = [10, 20, 50, 100];
$: metricsInHistograms = selectedCluster $: selectedHistograms = selectedCluster
? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || [] ? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
: ccconfig.user_view_histogramMetrics || []; : ccconfig['user_view_histogramMetrics'] || [];
const client = getContextClient(); const client = getContextClient();
$: stats = queryStore({ $: stats = queryStore({
client: client, client: client,
query: gql` query: gql`
query ($jobFilters: [JobFilter!]!, $metricsInHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) { query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) {
jobsStatistics(filter: $jobFilters, metrics: $metricsInHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) { jobsStatistics(filter: $jobFilters, metrics: $selectedHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) {
totalJobs totalJobs
shortJobs shortJobs
totalWalltime totalWalltime
@ -104,7 +104,7 @@
} }
} }
`, `,
variables: { jobFilters, metricsInHistograms, numDurationBins, numMetricBins }, variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins },
}); });
onMount(() => filterComponent.updateFilters()); onMount(() => filterComponent.updateFilters());
@ -290,7 +290,7 @@
</InputGroup> </InputGroup>
</Col> </Col>
</Row> </Row>
{#if metricsInHistograms?.length > 0} {#if selectedHistograms?.length > 0}
{#if $stats.error} {#if $stats.error}
<Row> <Row>
<Col> <Col>
@ -357,6 +357,6 @@
<HistogramSelection <HistogramSelection
bind:cluster={selectedCluster} bind:cluster={selectedCluster}
bind:metricsInHistograms bind:selectedHistograms
bind:isOpen={isHistogramSelectionOpen} bind:isOpen={isHistogramSelectionOpen}
/> />

View File

@ -45,6 +45,14 @@
export let startTimeQuickSelect = false; export let startTimeQuickSelect = false;
export let matchedJobs = -2; export let matchedJobs = -2;
const startTimeSelectOptions = [
{ range: "", rangeLabel: "No Selection"},
{ range: "last6h", rangeLabel: "Last 6hrs"},
{ range: "last24h", rangeLabel: "Last 24hrs"},
{ range: "last7d", rangeLabel: "Last 7 days"},
{ range: "last30d", rangeLabel: "Last 30 days"}
];
let filters = { let filters = {
projectMatch: filterPresets.projectMatch || "contains", projectMatch: filterPresets.projectMatch || "contains",
userMatch: filterPresets.userMatch || "contains", userMatch: filterPresets.userMatch || "contains",
@ -56,7 +64,7 @@
filterPresets.states || filterPresets.state filterPresets.states || filterPresets.state
? [filterPresets.state].flat() ? [filterPresets.state].flat()
: allJobStates, : allJobStates,
startTime: filterPresets.startTime || { from: null, to: null }, startTime: filterPresets.startTime || { from: null, to: null, range: ""},
tags: filterPresets.tags || [], tags: filterPresets.tags || [],
duration: filterPresets.duration || { duration: filterPresets.duration || {
lessThan: null, lessThan: null,
@ -268,16 +276,17 @@
{#if startTimeQuickSelect} {#if startTimeQuickSelect}
<DropdownItem divider /> <DropdownItem divider />
<DropdownItem disabled>Start Time Quick Selection</DropdownItem> <DropdownItem disabled>Start Time Quick Selection</DropdownItem>
{#each [{ text: "Last 6hrs", range: "last6h" }, { text: "Last 24hrs", range: "last24h" }, { text: "Last 7 days", range: "last7d" }, { text: "Last 30 days", range: "last30d" }] as { text, range }} {#each startTimeSelectOptions.filter((stso) => stso.range !== "") as { rangeLabel, range }}
<DropdownItem <DropdownItem
on:click={() => { on:click={() => {
filters.startTime.from = null
filters.startTime.to = null
filters.startTime.range = range; filters.startTime.range = range;
filters.startTime.text = text;
updateFilters(); updateFilters();
}} }}
> >
<Icon name="calendar-range" /> <Icon name="calendar-range" />
{text} {rangeLabel}
</DropdownItem> </DropdownItem>
{/each} {/each}
{/if} {/if}
@ -316,7 +325,7 @@
{#if filters.startTime.range} {#if filters.startTime.range}
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}> <Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
{filters?.startTime?.text ? filters.startTime.text : filters.startTime.range } {startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel }
</Info> </Info>
{/if} {/if}
@ -414,11 +423,8 @@
bind:from={filters.startTime.from} bind:from={filters.startTime.from}
bind:to={filters.startTime.to} bind:to={filters.startTime.to}
bind:range={filters.startTime.range} bind:range={filters.startTime.range}
on:set-filter={() => { {startTimeSelectOptions}
delete filters.startTime["text"]; on:set-filter={() => updateFilters()}
delete filters.startTime["range"];
updateFilters();
}}
/> />
<Duration <Duration

View File

@ -43,6 +43,10 @@
<ModalBody> <ModalBody>
{#if $initialized} {#if $initialized}
<h4>Cluster</h4> <h4>Cluster</h4>
{#if disableClusterSelection}
<Button color="info" class="w-100 mb-2" disabled><b>Info: Cluster Selection Disabled in This View</b></Button>
<Button outline color="primary" class="w-100 mb-2" disabled><b>Selected Cluster: {cluster}</b></Button>
{:else}
<ListGroup> <ListGroup>
<ListGroupItem <ListGroupItem
disabled={disableClusterSelection} disabled={disableClusterSelection}
@ -64,6 +68,7 @@
{/each} {/each}
</ListGroup> </ListGroup>
{/if} {/if}
{/if}
{#if $initialized && pendingCluster != null} {#if $initialized && pendingCluster != null}
<br /> <br />
<h4>Partiton</h4> <h4>Partiton</h4>

View File

@ -17,7 +17,6 @@
import { parse, format, sub } from "date-fns"; import { parse, format, sub } from "date-fns";
import { import {
Row, Row,
Col,
Button, Button,
Input, Input,
Modal, Modal,
@ -34,8 +33,7 @@
export let from = null; export let from = null;
export let to = null; export let to = null;
export let range = ""; export let range = "";
export let startTimeSelectOptions;
let pendingFrom, pendingTo;
const now = new Date(Date.now()); const now = new Date(Date.now());
const ago = sub(now, { months: 1 }); const ago = sub(now, { months: 1 });
@ -48,12 +46,24 @@
time: format(now, "HH:mm"), time: format(now, "HH:mm"),
}; };
function reset() { $: pendingFrom = (from == null) ? defaultFrom : fromRFC3339(from)
pendingFrom = from == null ? defaultFrom : fromRFC3339(from); $: pendingTo = (to == null) ? defaultTo : fromRFC3339(to)
pendingTo = to == null ? defaultTo : fromRFC3339(to); $: pendingRange = range
}
reset(); $: isModified =
(from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, "59")) &&
(range != pendingRange) &&
!(
from == null &&
pendingFrom.date == "0000-00-00" &&
pendingFrom.time == "00:00"
) &&
!(
to == null &&
pendingTo.date == "0000-00-00" &&
pendingTo.time == "00:00"
) &&
!( range == "" && pendingRange == "");
function toRFC3339({ date, time }, secs = "00") { function toRFC3339({ date, time }, secs = "00") {
const parsedDate = parse( const parsedDate = parse(
@ -71,19 +81,6 @@
time: format(parsedDate, "HH:mm"), time: format(parsedDate, "HH:mm"),
}; };
} }
$: isModified =
(from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, "59")) &&
!(
from == null &&
pendingFrom.date == "0000-00-00" &&
pendingFrom.time == "00:00"
) &&
!(
to == null &&
pendingTo.date == "0000-00-00" &&
pendingTo.time == "00:00"
);
</script> </script>
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}> <Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
@ -92,31 +89,59 @@
{#if range !== ""} {#if range !== ""}
<h4>Current Range</h4> <h4>Current Range</h4>
<Row> <Row>
<Col> <FormGroup class="col">
<Input type="text" value={range} disabled/> <Input type ="select" bind:value={pendingRange} >
</Col> {#each startTimeSelectOptions as { rangeLabel, range }}
<option label={rangeLabel} value={range}/>
{/each}
</Input>
</FormGroup>
</Row> </Row>
{/if} {/if}
<h4>From</h4> <h4>From</h4>
<Row> <Row>
<FormGroup class="col"> <FormGroup class="col">
<Input type="date" bind:value={pendingFrom.date} /> <Input type="date" bind:value={pendingFrom.date} disabled={pendingRange !== ""}/>
</FormGroup> </FormGroup>
<FormGroup class="col"> <FormGroup class="col">
<Input type="time" bind:value={pendingFrom.time} /> <Input type="time" bind:value={pendingFrom.time} disabled={pendingRange !== ""}/>
</FormGroup> </FormGroup>
</Row> </Row>
<h4>To</h4> <h4>To</h4>
<Row> <Row>
<FormGroup class="col"> <FormGroup class="col">
<Input type="date" bind:value={pendingTo.date} /> <Input type="date" bind:value={pendingTo.date} disabled={pendingRange !== ""}/>
</FormGroup> </FormGroup>
<FormGroup class="col"> <FormGroup class="col">
<Input type="time" bind:value={pendingTo.time} /> <Input type="time" bind:value={pendingTo.time} disabled={pendingRange !== ""}/>
</FormGroup> </FormGroup>
</Row> </Row>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
{#if pendingRange !== ""}
<Button
color="warning"
disabled={pendingRange === ""}
on:click={() => {
pendingRange = ""
}}
>
Reset Range
</Button>
<Button
color="primary"
disabled={pendingRange === ""}
on:click={() => {
isOpen = false;
from = null;
to = null;
range = pendingRange;
dispatch("set-filter", { from, to, range });
}}
>
Close & Apply Range
</Button>
{:else}
<Button <Button
color="primary" color="primary"
disabled={pendingFrom.date == "0000-00-00" || disabled={pendingFrom.date == "0000-00-00" ||
@ -125,19 +150,21 @@
isOpen = false; isOpen = false;
from = toRFC3339(pendingFrom); from = toRFC3339(pendingFrom);
to = toRFC3339(pendingTo, "59"); to = toRFC3339(pendingTo, "59");
dispatch("set-filter", { from, to }); range = "";
dispatch("set-filter", { from, to, range });
}} }}
> >
Close & Apply Close & Apply Dates
</Button> </Button>
{/if}
<Button <Button
color="danger" color="danger"
on:click={() => { on:click={() => {
isOpen = false; isOpen = false;
from = null; from = null;
to = null; to = null;
reset(); range = "";
dispatch("set-filter", { from, to }); dispatch("set-filter", { from, to, range });
}}>Reset</Button }}>Reset</Button
> >
<Button on:click={() => (isOpen = false)}>Close</Button> <Button on:click={() => (isOpen = false)}>Close</Button>

View File

@ -179,7 +179,7 @@
function render(plotData) { function render(plotData) {
if (plotData) { if (plotData) {
const opts = { const opts = {
title: "", title: "CPU Roofline Diagram",
mode: 2, mode: 2,
width: width, width: width,
height: height, height: height,

View File

@ -3,7 +3,7 @@
Properties: Properties:
- `cluster String`: Currently selected cluster - `cluster String`: Currently selected cluster
- `metricsInHistograms [String]`: The currently selected metrics to display as histogram - `selectedHistograms [String]`: The currently selected metrics to display as histogram
- ìsOpen Bool`: Is selection opened - ìsOpen Bool`: Is selection opened
--> -->
@ -21,21 +21,26 @@
import { gql, getContextClient, mutationStore } from "@urql/svelte"; import { gql, getContextClient, mutationStore } from "@urql/svelte";
export let cluster; export let cluster;
export let metricsInHistograms; export let selectedHistograms;
export let isOpen; export let isOpen;
const client = getContextClient(); const client = getContextClient();
const initialized = getContext("initialized"); const initialized = getContext("initialized");
let availableMetrics = [] function loadHistoMetrics(isInitialized, thisCluster) {
if (!isInitialized) return [];
function loadHistoMetrics(isInitialized) { if (!thisCluster) {
if (!isInitialized) return; return getContext("globalMetrics")
const rawAvailableMetrics = getContext("globalMetrics").filter((gm) => gm?.footprint).map((fgm) => { return fgm.name }) .filter((gm) => gm?.footprint)
availableMetrics = [...rawAvailableMetrics] .map((fgm) => { return fgm.name })
} else {
return getContext("globalMetrics")
.filter((gm) => gm?.availability.find((av) => av.cluster == thisCluster))
.filter((agm) => agm?.footprint)
.map((afgm) => { return afgm.name })
}
} }
let pendingMetrics = [...metricsInHistograms]; // Copy
const updateConfigurationMutation = ({ name, value }) => { const updateConfigurationMutation = ({ name, value }) => {
return mutationStore({ return mutationStore({
@ -61,17 +66,16 @@
} }
function closeAndApply() { function closeAndApply() {
metricsInHistograms = [...pendingMetrics]; // Set for parent
isOpen = !isOpen; isOpen = !isOpen;
updateConfiguration({ updateConfiguration({
name: cluster name: cluster
? `user_view_histogramMetrics:${cluster}` ? `user_view_histogramMetrics:${cluster}`
: "user_view_histogramMetrics", : "user_view_histogramMetrics",
value: metricsInHistograms, value: selectedHistograms,
}); });
} }
$: loadHistoMetrics($initialized); $: availableMetrics = loadHistoMetrics($initialized, cluster);
</script> </script>
@ -81,7 +85,7 @@
<ListGroup> <ListGroup>
{#each availableMetrics as metric (metric)} {#each availableMetrics as metric (metric)}
<ListGroupItem> <ListGroupItem>
<input type="checkbox" bind:group={pendingMetrics} value={metric} /> <input type="checkbox" bind:group={selectedHistograms} value={metric} />
{metric} {metric}
</ListGroupItem> </ListGroupItem>
{/each} {/each}

View File

@ -18,6 +18,8 @@
InputGroup, InputGroup,
InputGroupText, InputGroupText,
Icon, Icon,
Row,
Col
} from "@sveltestrap/sveltestrap"; } from "@sveltestrap/sveltestrap";
import { maxScope } from "../generic/utils.js"; import { maxScope } from "../generic/utils.js";
import StatsTableEntry from "./StatsTableEntry.svelte"; import StatsTableEntry from "./StatsTableEntry.svelte";
@ -26,7 +28,7 @@
export let job; export let job;
export let jobMetrics; export let jobMetrics;
const allMetrics = [...new Set(jobMetrics.map((m) => m.name))].sort() const sortedJobMetrics = [...new Set(jobMetrics.map((m) => m.name))].sort()
const scopesForMetric = (metric) => const scopesForMetric = (metric) =>
jobMetrics.filter((jm) => jm.name == metric).map((jm) => jm.scope); jobMetrics.filter((jm) => jm.name == metric).map((jm) => jm.scope);
@ -34,11 +36,12 @@
selectedScopes = {}, selectedScopes = {},
sorting = {}, sorting = {},
isMetricSelectionOpen = false, isMetricSelectionOpen = false,
availableMetrics = new Set(),
selectedMetrics = selectedMetrics =
getContext("cc-config")[`job_view_nodestats_selectedMetrics:${job.cluster}`] || getContext("cc-config")[`job_view_nodestats_selectedMetrics:${job.cluster}`] ||
getContext("cc-config")["job_view_nodestats_selectedMetrics"]; getContext("cc-config")["job_view_nodestats_selectedMetrics"];
for (let metric of allMetrics) { for (let metric of sortedJobMetrics) {
// Not Exclusive or Multi-Node: get maxScope directly (mostly: node) // Not Exclusive or Multi-Node: get maxScope directly (mostly: node)
// -> Else: Load smallest available granularity as default as per availability // -> Else: Load smallest available granularity as default as per availability
const availableScopes = scopesForMetric(metric); const availableScopes = scopesForMetric(metric);
@ -95,15 +98,19 @@
}; };
</script> </script>
<Row>
<Col class="m-2">
<Button outline on:click={() => (isMetricSelectionOpen = true)} class="w-auto px-2" color="primary">
Select Metrics (Selected {selectedMetrics.length} of {availableMetrics.size} available)
</Button>
</Col>
</Row>
<hr class="mb-1 mt-1"/>
<Table class="mb-0"> <Table class="mb-0">
<thead> <thead>
<!-- Header Row 1: Selectors --> <!-- Header Row 1: Selectors -->
<tr> <tr>
<th> <th/>
<Button outline on:click={() => (isMetricSelectionOpen = true)} class="w-100 px-2" color="primary">
Select Metrics
</Button>
</th>
{#each selectedMetrics as metric} {#each selectedMetrics as metric}
<!-- To Match Row-2 Header Field Count--> <!-- To Match Row-2 Header Field Count-->
<th colspan={selectedScopes[metric] == "node" ? 3 : 4}> <th colspan={selectedScopes[metric] == "node" ? 3 : 4}>
@ -163,7 +170,7 @@
<MetricSelection <MetricSelection
cluster={job.cluster} cluster={job.cluster}
configName="job_view_nodestats_selectedMetrics" configName="job_view_nodestats_selectedMetrics"
allMetrics={new Set(allMetrics)} bind:allMetrics={availableMetrics}
bind:metrics={selectedMetrics} bind:metrics={selectedMetrics}
bind:isOpen={isMetricSelectionOpen} bind:isOpen={isMetricSelectionOpen}
/> />

View File

@ -102,6 +102,19 @@
Shared Shared
</Button> </Button>
</InputGroup> </InputGroup>
<!-- Fallback -->
{:else if nodeJobsData.jobs.count >= 1}
<InputGroup>
<InputGroupText>
<Icon name="circle-fill"/>
</InputGroupText>
<InputGroupText>
Status
</InputGroupText>
<Button color="success" disabled>
Allocated Jobs
</Button>
</InputGroup>
{:else} {:else}
<InputGroup> <InputGroup>
<InputGroupText> <InputGroupText>