mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-01 11:13:50 +02:00
migrate system view, node list and node overview
This commit is contained in:
parent
a0190f8f40
commit
f471214ef7
@ -28,72 +28,74 @@
|
|||||||
import TimeSelection from "./generic/select/TimeSelection.svelte";
|
import TimeSelection from "./generic/select/TimeSelection.svelte";
|
||||||
import Refresher from "./generic/helper/Refresher.svelte";
|
import Refresher from "./generic/helper/Refresher.svelte";
|
||||||
|
|
||||||
export let displayType;
|
|
||||||
export let cluster = null;
|
|
||||||
export let subCluster = null;
|
|
||||||
export let from = null;
|
|
||||||
export let to = null;
|
|
||||||
|
|
||||||
const { query: initq } = init();
|
|
||||||
|
|
||||||
console.assert(
|
|
||||||
displayType == "OVERVIEW" || displayType == "LIST",
|
|
||||||
"Invalid nodes displayType provided!",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (from == null || to == null) {
|
|
||||||
to = new Date(Date.now());
|
|
||||||
from = new Date(to.getTime());
|
|
||||||
from.setHours(from.getHours() - 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialized = getContext("initialized");
|
|
||||||
const ccconfig = getContext("cc-config");
|
|
||||||
const globalMetrics = getContext("globalMetrics");
|
|
||||||
const displayNodeOverview = (displayType === 'OVERVIEW')
|
|
||||||
|
|
||||||
const resampleConfig = getContext("resampling") || null;
|
|
||||||
const resampleResolutions = resampleConfig ? [...resampleConfig.resolutions] : [];
|
|
||||||
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
|
|
||||||
let selectedResolution = resampleConfig ? resampleDefault : 0;
|
|
||||||
|
|
||||||
let hostnameFilter = "";
|
|
||||||
let pendingHostnameFilter = "";
|
|
||||||
let selectedMetric = ccconfig.system_view_selectedMetric || "";
|
|
||||||
let selectedMetrics = (
|
|
||||||
ccconfig[`node_list_selectedMetrics:${cluster}:${subCluster}`] ||
|
|
||||||
ccconfig[`node_list_selectedMetrics:${cluster}`]
|
|
||||||
) || [ccconfig.system_view_selectedMetric];
|
|
||||||
let isMetricsSelectionOpen = false;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Note 1: "Sorting" as use-case ignored for now, probably default to alphanumerical on hostnames of cluster (handled in frontend at the moment)
|
Note 1: "Sorting" as use-case ignored for now, probably default to alphanumerical on hostnames of cluster (handled in frontend at the moment)
|
||||||
Note 2: Add Idle State Filter (== No allocated Jobs) [Frontend?] : Cannot be handled by CCMS, requires secondary job query and refiltering of visible nodes
|
Note 2: Add Idle State Filter (== No allocated Jobs) [Frontend?] : Cannot be handled by CCMS, requires secondary job query and refiltering of visible nodes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let systemMetrics = [];
|
/* Scelte 5 Props */
|
||||||
let systemUnits = {};
|
let {
|
||||||
|
displayType,
|
||||||
|
cluster = null,
|
||||||
|
subCluster = null,
|
||||||
|
fromPreset = null,
|
||||||
|
toPreset = null,
|
||||||
|
} = $props();
|
||||||
|
|
||||||
function loadMetrics(isInitialized) {
|
/* Const Init */
|
||||||
if (!isInitialized) return
|
const { query: initq } = init();
|
||||||
systemMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))]
|
const displayNodeOverview = (displayType === 'OVERVIEW');
|
||||||
|
const ccconfig = getContext("cc-config");
|
||||||
|
const initialized = getContext("initialized");
|
||||||
|
const globalMetrics = getContext("globalMetrics");
|
||||||
|
const resampleConfig = getContext("resampling") || null;
|
||||||
|
|
||||||
|
const resampleResolutions = resampleConfig ? [...resampleConfig.resolutions] : [];
|
||||||
|
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
|
||||||
|
const nowDate = new Date(Date.now());
|
||||||
|
|
||||||
|
/* State Init */
|
||||||
|
let to = $state(toPreset || new Date(Date.now()));
|
||||||
|
let from = $state(fromPreset || new Date(nowDate.setHours(nowDate.getHours() - 12)));
|
||||||
|
let selectedResolution = $state(resampleConfig ? resampleDefault : 0);
|
||||||
|
let hostnameFilter = $state("");
|
||||||
|
let pendingHostnameFilter = $state("");
|
||||||
|
let isMetricsSelectionOpen = $state(false);
|
||||||
|
let selectedMetric = $state(ccconfig.system_view_selectedMetric || "");
|
||||||
|
let selectedMetrics = $state((
|
||||||
|
ccconfig[`node_list_selectedMetrics:${cluster}:${subCluster}`] ||
|
||||||
|
ccconfig[`node_list_selectedMetrics:${cluster}`]
|
||||||
|
) || [ccconfig.system_view_selectedMetric]);
|
||||||
|
|
||||||
|
/* Derived States */
|
||||||
|
const systemMetrics = $derived($initialized ? [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))] : []);
|
||||||
|
const presetSystemUnits = $derived(loadUnits(systemMetrics));
|
||||||
|
|
||||||
|
/* Effects */
|
||||||
|
$effect(() => {
|
||||||
|
// OnMount: Ping Var, without this, OVERVIEW metric select is empty (reason tbd)
|
||||||
|
systemMetrics
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
function loadUnits(systemMetrics) {
|
||||||
|
let pendingUnits = {};
|
||||||
|
if (systemMetrics.length > 0) {
|
||||||
for (let sm of systemMetrics) {
|
for (let sm of systemMetrics) {
|
||||||
systemUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "")
|
pendingUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "")
|
||||||
}
|
};
|
||||||
if (!selectedMetric) selectedMetric = systemMetrics[0].name
|
};
|
||||||
}
|
return {...pendingUnits};
|
||||||
|
};
|
||||||
|
|
||||||
$: loadMetrics($initialized)
|
// Wait after input for some time to prevent too many requests
|
||||||
|
let timeoutId = null;
|
||||||
$: if (displayNodeOverview) {
|
function updateHostnameFilter() {
|
||||||
selectedMetrics = [selectedMetric]
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
}
|
timeoutId = setTimeout(function () {
|
||||||
|
|
||||||
$: { // Wait after input for some time to prevent too many requests
|
|
||||||
setTimeout(function () {
|
|
||||||
hostnameFilter = pendingHostnameFilter;
|
hostnameFilter = pendingHostnameFilter;
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- ROW1: Tools-->
|
<!-- ROW1: Tools-->
|
||||||
@ -108,7 +110,7 @@
|
|||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => (isMetricsSelectionOpen = true)}
|
onclick={() => (isMetricsSelectionOpen = true)}
|
||||||
>
|
>
|
||||||
{selectedMetrics.length} selected
|
{selectedMetrics.length} selected
|
||||||
</Button>
|
</Button>
|
||||||
@ -139,6 +141,7 @@
|
|||||||
placeholder="Filter hostname ..."
|
placeholder="Filter hostname ..."
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={pendingHostnameFilter}
|
bind:value={pendingHostnameFilter}
|
||||||
|
oninput={updateHostnameFilter}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
@ -153,15 +156,18 @@
|
|||||||
<InputGroupText><Icon name="graph-up" /></InputGroupText>
|
<InputGroupText><Icon name="graph-up" /></InputGroupText>
|
||||||
<InputGroupText>Metric</InputGroupText>
|
<InputGroupText>Metric</InputGroupText>
|
||||||
<Input type="select" bind:value={selectedMetric}>
|
<Input type="select" bind:value={selectedMetric}>
|
||||||
{#each systemMetrics as metric}
|
{#each systemMetrics as metric (metric.name)}
|
||||||
<option value={metric.name}
|
<option value={metric.name}
|
||||||
>{metric.name} {systemUnits[metric.name] ? "("+systemUnits[metric.name]+")" : ""}</option
|
>{metric.name} {presetSystemUnits[metric.name] ? "("+presetSystemUnits[metric.name]+")" : ""}</option
|
||||||
>
|
>
|
||||||
|
{:else}
|
||||||
|
<option disabled>No available options</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Input>
|
</Input>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Col>
|
</Col>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
<!-- Refresh Col-->
|
<!-- Refresh Col-->
|
||||||
<Col class="mt-2 mt-lg-0">
|
<Col class="mt-2 mt-lg-0">
|
||||||
<Refresher
|
<Refresher
|
||||||
@ -172,7 +178,6 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
{/if}
|
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<!-- ROW2: Content-->
|
<!-- ROW2: Content-->
|
||||||
@ -185,14 +190,15 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#if displayNodeOverview}
|
{#if displayNodeOverview}
|
||||||
<!-- ROW2-1: Node Overview (Grid Included)-->
|
<!-- ROW2-1: Node Overview (Grid Included)-->
|
||||||
<NodeOverview {cluster} {subCluster} {ccconfig} {selectedMetrics} {from} {to} {hostnameFilter}/>
|
<NodeOverview {cluster} {ccconfig} {selectedMetric} {from} {to} {hostnameFilter}/>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- ROW2-2: Node List (Grid Included)-->
|
<!-- ROW2-2: Node List (Grid Included)-->
|
||||||
<NodeList {cluster} {subCluster} {ccconfig} {selectedMetrics} {selectedResolution} {hostnameFilter} {from} {to} {systemUnits}/>
|
<NodeList {cluster} {subCluster} {ccconfig} {selectedMetrics} {selectedResolution} {hostnameFilter} {from} {to} {presetSystemUnits}/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<MetricSelection
|
{#if !displayNodeOverview}
|
||||||
|
<MetricSelection
|
||||||
bind:isOpen={isMetricsSelectionOpen}
|
bind:isOpen={isMetricsSelectionOpen}
|
||||||
presetMetrics={selectedMetrics}
|
presetMetrics={selectedMetrics}
|
||||||
{cluster}
|
{cluster}
|
||||||
@ -201,4 +207,5 @@
|
|||||||
applyMetrics={(newMetrics) =>
|
applyMetrics={(newMetrics) =>
|
||||||
selectedMetrics = [...newMetrics]
|
selectedMetrics = [...newMetrics]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
|
@ -8,8 +8,8 @@ mount(Systems, {
|
|||||||
displayType: displayType,
|
displayType: displayType,
|
||||||
cluster: infos.cluster,
|
cluster: infos.cluster,
|
||||||
subCluster: infos.subCluster,
|
subCluster: infos.subCluster,
|
||||||
from: infos.from,
|
fromPreset: infos.from,
|
||||||
to: infos.to
|
toPreset: infos.to
|
||||||
},
|
},
|
||||||
context: new Map([
|
context: new Map([
|
||||||
['cc-config', clusterCockpitConfig],
|
['cc-config', clusterCockpitConfig],
|
||||||
|
@ -10,38 +10,28 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte";
|
|
||||||
import { queryStore, gql, getContextClient, mutationStore } from "@urql/svelte";
|
import { queryStore, gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||||
import { Row, Col, Card, Table, Spinner } from "@sveltestrap/sveltestrap";
|
import { Row, Col, Card, Table, Spinner } from "@sveltestrap/sveltestrap";
|
||||||
import { stickyHeader } from "../generic/utils.js";
|
import { stickyHeader } from "../generic/utils.js";
|
||||||
import NodeListRow from "./nodelist/NodeListRow.svelte";
|
import NodeListRow from "./nodelist/NodeListRow.svelte";
|
||||||
import Pagination from "../generic/joblist/Pagination.svelte";
|
import Pagination from "../generic/joblist/Pagination.svelte";
|
||||||
|
|
||||||
export let cluster;
|
/* Svelte 5 Props */
|
||||||
export let subCluster = "";
|
let {
|
||||||
export let ccconfig = null;
|
cluster,
|
||||||
export let selectedMetrics = [];
|
subCluster = "",
|
||||||
export let selectedResolution = 0;
|
ccconfig = null,
|
||||||
export let hostnameFilter = "";
|
selectedMetrics = [],
|
||||||
export let systemUnits = null;
|
selectedResolution = 0,
|
||||||
export let from = null;
|
hostnameFilter = "",
|
||||||
export let to = null;
|
presetSystemUnits = null,
|
||||||
|
from = null,
|
||||||
|
to = null
|
||||||
|
} = $props();
|
||||||
|
|
||||||
// Decouple from Job List Paging Params?
|
/* Const Init */
|
||||||
let usePaging = ccconfig?.node_list_usePaging || false
|
|
||||||
let itemsPerPage = usePaging ? (ccconfig?.plot_list_nodesPerPage || 10) : 10;
|
|
||||||
let page = 1;
|
|
||||||
let paging = { itemsPerPage, page };
|
|
||||||
|
|
||||||
let headerPaddingTop = 0;
|
|
||||||
stickyHeader(
|
|
||||||
".cc-table-wrapper > table.table >thead > tr > th.position-sticky:nth-child(1)",
|
|
||||||
(x) => (headerPaddingTop = x),
|
|
||||||
);
|
|
||||||
|
|
||||||
// const { query: initq } = init();
|
|
||||||
const initialized = getContext("initialized");
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
const usePaging = ccconfig?.node_list_usePaging || false;
|
||||||
const nodeListQuery = gql`
|
const nodeListQuery = gql`
|
||||||
query ($cluster: String!, $subCluster: String!, $nodeFilter: String!, $metrics: [String!], $scopes: [MetricScope!]!, $from: Time!, $to: Time!, $paging: PageRequest!, $selectedResolution: Int) {
|
query ($cluster: String!, $subCluster: String!, $nodeFilter: String!, $metrics: [String!], $scopes: [MetricScope!]!, $from: Time!, $to: Time!, $paging: PageRequest!, $selectedResolution: Int) {
|
||||||
nodeMetricsList(
|
nodeMetricsList(
|
||||||
@ -91,51 +81,17 @@
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const updateConfigurationMutation = ({ name, value }) => {
|
/* State Init */
|
||||||
return mutationStore({
|
let nodes = $state([]);
|
||||||
client: client,
|
let page = $state(1);
|
||||||
query: gql`
|
let itemsPerPage = $state(usePaging ? (ccconfig?.plot_list_nodesPerPage || 10) : 10);
|
||||||
mutation ($name: String!, $value: String!) {
|
let headerPaddingTop = $state(0);
|
||||||
updateConfiguration(name: $name, value: $value)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: { name, value },
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Decouple from Job List Paging Params?
|
/* Derived */
|
||||||
function updateConfiguration(value, page) {
|
const paging = $derived({ itemsPerPage, page });
|
||||||
updateConfigurationMutation({
|
const matchedNodes = $derived($nodesQuery?.data?.nodeMetricsList?.totalNodes || 0);
|
||||||
name: "plot_list_nodesPerPage",
|
|
||||||
value: value,
|
|
||||||
}).subscribe((res) => {
|
|
||||||
if (res.fetching === false && !res.error) {
|
|
||||||
nodes = [] // Empty List
|
|
||||||
paging = { itemsPerPage: value, page: page }; // Trigger reload of nodeList
|
|
||||||
} else if (res.fetching === false && res.error) {
|
|
||||||
throw res.error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!usePaging) {
|
const nodesQuery = $derived(queryStore({
|
||||||
window.addEventListener('scroll', () => {
|
|
||||||
let {
|
|
||||||
scrollTop,
|
|
||||||
scrollHeight,
|
|
||||||
clientHeight
|
|
||||||
} = document.documentElement;
|
|
||||||
|
|
||||||
// Add 100 px offset to trigger load earlier
|
|
||||||
if (scrollTop + clientHeight >= scrollHeight - 100 && $nodesQuery?.data != null && $nodesQuery.data?.nodeMetricsList.hasNextPage) {
|
|
||||||
let pendingPaging = { ...paging }
|
|
||||||
pendingPaging.page += 1
|
|
||||||
paging = pendingPaging
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$: nodesQuery = queryStore({
|
|
||||||
client: client,
|
client: client,
|
||||||
query: nodeListQuery,
|
query: nodeListQuery,
|
||||||
variables: {
|
variables: {
|
||||||
@ -150,24 +106,92 @@
|
|||||||
selectedResolution: selectedResolution,
|
selectedResolution: selectedResolution,
|
||||||
},
|
},
|
||||||
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
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* Effects */
|
||||||
|
$effect(() => {
|
||||||
|
if (!usePaging) {
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
let {
|
||||||
|
scrollTop,
|
||||||
|
scrollHeight,
|
||||||
|
clientHeight
|
||||||
|
} = document.documentElement;
|
||||||
|
|
||||||
|
// Add 100 px offset to trigger load earlier
|
||||||
|
if (scrollTop + clientHeight >= scrollHeight - 100 && $nodesQuery?.data != null && $nodesQuery.data?.nodeMetricsList.hasNextPage) {
|
||||||
|
page += 1
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let nodes = [];
|
$effect(() => {
|
||||||
$: if ($initialized && $nodesQuery.data) {
|
handleNodes($nodesQuery?.data?.nodeMetricsList?.items);
|
||||||
if (usePaging) {
|
});
|
||||||
nodes = [...$nodesQuery.data.nodeMetricsList.items].sort((a, b) => a.host.localeCompare(b.host));
|
|
||||||
} else {
|
|
||||||
nodes = nodes.concat([...$nodesQuery.data.nodeMetricsList.items].sort((a, b) => a.host.localeCompare(b.host)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (!usePaging && (selectedMetrics || selectedResolution || hostnameFilter || from || to)) {
|
$effect(() => {
|
||||||
// Continous Scroll: Reset list and paging if parameters change: Existing entries will not match new selections
|
// Triggers (Except Paging)
|
||||||
|
from, to
|
||||||
|
selectedMetrics, selectedResolution
|
||||||
|
hostnameFilter
|
||||||
|
// Continous Scroll: Reset nodes and paging if parameters change: Existing entries will not match new selections
|
||||||
|
if (!usePaging) {
|
||||||
nodes = [];
|
nodes = [];
|
||||||
paging = { itemsPerPage, page: 1 };
|
page = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
function handleNodes(data) {
|
||||||
|
if (data) {
|
||||||
|
if (usePaging || nodes.length == 0) {
|
||||||
|
nodes = [...data].sort((a, b) => a.host.localeCompare(b.host));
|
||||||
|
} else {
|
||||||
|
// Workaround to ignore secondary store triggers (reason tbd)
|
||||||
|
const oldNodes = $state.snapshot(nodes)
|
||||||
|
const newNodes = [...data].map((d) => d.host)
|
||||||
|
if (!oldNodes.some((n) => newNodes.includes(n.host))) {
|
||||||
|
nodes = nodes.concat([...data].sort((a, b) => a.host.localeCompare(b.host)))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decouple from Job List Paging Params?
|
||||||
|
function updateConfiguration(newItems, newPage) {
|
||||||
|
updateConfigurationMutation({
|
||||||
|
name: "plot_list_nodesPerPage",
|
||||||
|
value: newItems.toString(),
|
||||||
|
}).subscribe((res) => {
|
||||||
|
if (res.fetching === false && !res.error) {
|
||||||
|
nodes = []; // Empty List
|
||||||
|
itemsPerPage = newItems;
|
||||||
|
page = newPage; // Trigger reload of nodeList
|
||||||
|
} else if (res.fetching === false && res.error) {
|
||||||
|
throw res.error;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$: matchedNodes = $nodesQuery.data?.nodeMetricsList.totalNodes || matchedNodes;
|
/* Const Functions */
|
||||||
|
const updateConfigurationMutation = ({ name, value }) => {
|
||||||
|
return mutationStore({
|
||||||
|
client: client,
|
||||||
|
query: gql`
|
||||||
|
mutation ($name: String!, $value: String!) {
|
||||||
|
updateConfiguration(name: $name, value: $value)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: { name, value },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Init Header */
|
||||||
|
stickyHeader(
|
||||||
|
".cc-table-wrapper > table.table >thead > tr > th.position-sticky:nth-child(1)",
|
||||||
|
(x) => (headerPaddingTop = x),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
@ -192,7 +216,7 @@
|
|||||||
scope="col"
|
scope="col"
|
||||||
style="padding-top: {headerPaddingTop}px"
|
style="padding-top: {headerPaddingTop}px"
|
||||||
>
|
>
|
||||||
{metric} ({systemUnits[metric]})
|
{metric} ({presetSystemUnits[metric]})
|
||||||
</th>
|
</th>
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
@ -244,10 +268,11 @@
|
|||||||
totalItems={matchedNodes}
|
totalItems={matchedNodes}
|
||||||
on:update-paging={({ detail }) => {
|
on:update-paging={({ detail }) => {
|
||||||
if (detail.itemsPerPage != itemsPerPage) {
|
if (detail.itemsPerPage != itemsPerPage) {
|
||||||
updateConfiguration(detail.itemsPerPage.toString(), detail.page);
|
updateConfiguration(detail.itemsPerPage, detail.page);
|
||||||
} else {
|
} else {
|
||||||
nodes = []
|
nodes = [];
|
||||||
paging = { itemsPerPage: detail.itemsPerPage, page: detail.page };
|
itemsPerPage = detail.itemsPerPage;
|
||||||
|
page = detail.page;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -10,20 +10,26 @@
|
|||||||
<script>
|
<script>
|
||||||
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
||||||
import { Row, Col, Card, Spinner } from "@sveltestrap/sveltestrap";
|
import { Row, Col, Card, Spinner } from "@sveltestrap/sveltestrap";
|
||||||
import { init, checkMetricsDisabled } from "../generic/utils.js";
|
import { checkMetricDisabled } from "../generic/utils.js";
|
||||||
import MetricPlot from "../generic/plots/MetricPlot.svelte";
|
import MetricPlot from "../generic/plots/MetricPlot.svelte";
|
||||||
|
|
||||||
export let ccconfig = null;
|
/* Svelte 5 Props */
|
||||||
export let cluster = "";
|
let {
|
||||||
export const subCluster = "";
|
ccconfig = null,
|
||||||
export let selectedMetrics = null;
|
cluster = "",
|
||||||
export let hostnameFilter = "";
|
selectedMetric = "",
|
||||||
export let from = null;
|
hostnameFilter = "",
|
||||||
export let to = null;
|
from = null,
|
||||||
|
to = null
|
||||||
|
} = $props();
|
||||||
|
|
||||||
const { query: initq } = init();
|
/* Const Init */
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
const nodeQuery = gql`
|
|
||||||
|
/* Derived */
|
||||||
|
const nodesQuery = $derived(queryStore({
|
||||||
|
client: client,
|
||||||
|
query: gql`
|
||||||
query ($cluster: String!, $metrics: [String!], $from: Time!, $to: Time!) {
|
query ($cluster: String!, $metrics: [String!], $from: Time!, $to: Time!) {
|
||||||
nodeMetrics(
|
nodeMetrics(
|
||||||
cluster: $cluster
|
cluster: $cluster
|
||||||
@ -54,45 +60,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`,
|
||||||
|
|
||||||
$: selectedMetric = selectedMetrics[0] ? selectedMetrics[0] : "";
|
|
||||||
|
|
||||||
$: nodesQuery = queryStore({
|
|
||||||
client: client,
|
|
||||||
query: nodeQuery,
|
|
||||||
variables: {
|
variables: {
|
||||||
cluster: cluster,
|
cluster: cluster,
|
||||||
metrics: selectedMetrics,
|
metrics: [selectedMetric],
|
||||||
from: from.toISOString(),
|
from: from,
|
||||||
to: to.toISOString(),
|
to: to,
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
|
const mappedData = $derived(handleQueryData($nodesQuery?.data));
|
||||||
|
const filteredData = $derived(mappedData.filter((h) => h.host.includes(hostnameFilter)));
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
function handleQueryData(queryData) {
|
||||||
let rawData = []
|
let rawData = []
|
||||||
$: if ($initq.data && $nodesQuery?.data) {
|
if (queryData) {
|
||||||
rawData = $nodesQuery?.data?.nodeMetrics.filter((h) => {
|
rawData = queryData.nodeMetrics.filter((h) => {
|
||||||
if (h.subCluster === '') { // Exclude nodes with empty subCluster field
|
if (h.subCluster !== '') { // Exclude nodes with empty subCluster field
|
||||||
console.warn('subCluster not configured for node', h.host)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return h.metrics.some(
|
return h.metrics.some(
|
||||||
(m) => selectedMetrics.includes(m.name) && m.scope == "node",
|
(m) => m?.name == selectedMetric && m.scope == "node",
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
let mappedData = []
|
let pendingMapped = [];
|
||||||
$: if (rawData?.length > 0) {
|
if (rawData.length > 0) {
|
||||||
mappedData = rawData.map((h) => ({
|
pendingMapped = rawData.map((h) => ({
|
||||||
host: h.host,
|
host: h.host,
|
||||||
subCluster: h.subCluster,
|
subCluster: h.subCluster,
|
||||||
data: h.metrics.filter(
|
data: h.metrics.filter(
|
||||||
(m) => selectedMetrics.includes(m.name) && m.scope == "node",
|
(m) => m?.name == selectedMetric && m.scope == "node",
|
||||||
),
|
),
|
||||||
disabled: checkMetricsDisabled(
|
disabled: checkMetricDisabled(
|
||||||
selectedMetrics,
|
selectedMetric,
|
||||||
cluster,
|
cluster,
|
||||||
h.subCluster,
|
h.subCluster,
|
||||||
),
|
),
|
||||||
@ -100,11 +102,7 @@
|
|||||||
.sort((a, b) => a.host.localeCompare(b.host))
|
.sort((a, b) => a.host.localeCompare(b.host))
|
||||||
}
|
}
|
||||||
|
|
||||||
let filteredData = []
|
return pendingMapped;
|
||||||
$: if (mappedData?.length > 0) {
|
|
||||||
filteredData = mappedData.filter((h) =>
|
|
||||||
h.host.includes(hostnameFilter)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -132,7 +130,7 @@
|
|||||||
>{item.host} ({item.subCluster})</a
|
>{item.host} ({item.subCluster})</a
|
||||||
>
|
>
|
||||||
</h4>
|
</h4>
|
||||||
{#if item?.disabled[selectedMetric]}
|
{#if item?.disabled}
|
||||||
<Card body class="mx-3" color="info"
|
<Card body class="mx-3" color="info"
|
||||||
>Metric disabled for subcluster <code
|
>Metric disabled for subcluster <code
|
||||||
>{selectedMetric}:{item.subCluster}</code
|
>{selectedMetric}:{item.subCluster}</code
|
||||||
@ -140,6 +138,8 @@
|
|||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- "No Data"-Warning included in MetricPlot-Component -->
|
<!-- "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
|
<MetricPlot
|
||||||
timestep={item.data[0].metric.timestep}
|
timestep={item.data[0].metric.timestep}
|
||||||
series={item.data[0].metric.series}
|
series={item.data[0].metric.series}
|
||||||
@ -148,6 +148,7 @@
|
|||||||
subCluster={item.subCluster}
|
subCluster={item.subCluster}
|
||||||
forNode
|
forNode
|
||||||
/>
|
/>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
{/each}
|
{/each}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user