Merge branch 'main' into feature/526-average-resample

This commit is contained in:
2026-06-17 06:30:54 +02:00
69 changed files with 5681 additions and 5268 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "cc-frontend",
"version": "1.5.2",
"version": "1.5.3",
"lockfileVersion": 4,
"requires": true,
"packages": {
"": {
"name": "cc-frontend",
"version": "1.5.2",
"version": "1.5.3",
"license": "MIT",
"dependencies": {
"@rollup/plugin-replace": "^6.0.3",
@@ -608,9 +608,9 @@
]
},
"node_modules/@sveltejs/acorn-typescript": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz",
"integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz",
"integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==",
"license": "MIT",
"peerDependencies": {
"acorn": "^8.9.0"
@@ -654,19 +654,6 @@
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@typescript-eslint/types": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz",
"integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@urql/core": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz",
@@ -803,9 +790,9 @@
}
},
"node_modules/devalue": {
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz",
"integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==",
"license": "MIT"
},
"node_modules/escape-latex": {
@@ -821,13 +808,20 @@
"license": "MIT"
},
"node_modules/esrap": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz",
"integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==",
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz",
"integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"peerDependencies": {
"@typescript-eslint/types": "^8.2.0"
},
"peerDependenciesMeta": {
"@typescript-eslint/types": {
"optional": true
}
}
},
"node_modules/estree-walker": {
@@ -968,9 +962,9 @@
}
},
"node_modules/mathjs": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-15.1.1.tgz",
"integrity": "sha512-rM668DTtpSzMVoh/cKAllyQVEbBApM5g//IMGD8vD7YlrIz9ITRr3SrdhjaDxcBNTdyETWwPebj2unZyHD7ZdA==",
"version": "15.2.0",
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-15.2.0.tgz",
"integrity": "sha512-UAQzSVob9rNLdGpqcFMYmSu9dkuLYy7Lr2hBEQS5SHQdknA9VppJz3cy2KkpMzTODunad6V6cNv+5kOLsePLow==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.26.10",
@@ -998,9 +992,9 @@
"license": "MIT"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -1134,9 +1128,9 @@
}
},
"node_modules/rollup-plugin-svelte/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1153,9 +1147,9 @@
"license": "MIT"
},
"node_modules/serialize-javascript": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz",
"integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==",
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz",
"integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -1207,23 +1201,23 @@
}
},
"node_modules/svelte": {
"version": "5.54.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.54.0.tgz",
"integrity": "sha512-TTDxwYnHkova6Wsyj1PGt9TByuWqvMoeY1bQiuAf2DM/JeDSMw7FjRKzk8K/5mJ99vGOKhbCqTDpyAKwjp4igg==",
"version": "5.55.10",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.10.tgz",
"integrity": "sha512-v9mFVBY1USosyIWdXE7Cg4AN0ywyKCMcAhONvli8doMowEhFhMdNLKD1j7O/UnsrdVTHaUOk/jv8hD/HClVy+g==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.5",
"@sveltejs/acorn-typescript": "^1.0.10",
"@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1",
"aria-query": "5.3.1",
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"devalue": "^5.6.4",
"devalue": "^5.8.1",
"esm-env": "^1.2.1",
"esrap": "^2.2.2",
"esrap": "^2.2.9",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",

View File

@@ -1,6 +1,6 @@
{
"name": "cc-frontend",
"version": "1.5.2",
"version": "1.5.3",
"license": "MIT",
"scripts": {
"build": "rollup -c",

View File

@@ -237,7 +237,7 @@
The following note was added by administrators:
</Card>
<Card body>
{@html thisJob.metaData.message}
<span style="white-space: pre-wrap">{thisJob.metaData.message}</span>
</Card>
</CardBody>
</TabPane>

View File

@@ -57,7 +57,7 @@
let entries = $state([]);
let loading = $state(false);
let error = $state(null);
let timer = $state(null);
let timer = null;
function levelColor(priority) {
if (priority <= 2) return "danger";

View File

@@ -54,11 +54,16 @@
const paging = { itemsPerPage: 50, page: 1 };
const sorting = { field: "startTime", type: "col", order: "DESC" };
const nodeMetricsQuery = gql`
query ($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
query (
$cluster: String!,
$nodes: [String!],
$from: Time!,
$to: Time!,
$nodeFilter: [NodeFilter!]!,
$sorting: OrderByInput!
) {
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
host
nodeState
metricHealth
subCluster
metrics {
name
@@ -79,7 +84,14 @@
}
}
}
}
},
nodeStatus: nodes(filter: $nodeFilter, order: $sorting) {
count
items {
schedulerState
healthState
}
}
}
`;
const nodeJobsQuery = gql`
@@ -146,6 +158,8 @@
nodes: [hostname],
from: from?.toISOString(),
to: to?.toISOString(),
nodeFilter: { hostname: { eq: hostname }},
sorting // $sorting unused in backend: Use placeholder
},
})
);
@@ -157,8 +171,8 @@
})
);
const thisNodeState = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.nodeState || 'notindb');
const thisMetricHealth = $derived($nodeMetricsData?.data?.nodeMetrics[0]?.metricHealth || 'unknown');
const thisNodeState = $derived($nodeMetricsData?.data?.nodeStatus?.items[0]?.schedulerState || 'notindb');
const thisMetricHealth = $derived($nodeMetricsData?.data?.nodeStatus?.items[0]?.healthState || 'unknown');
</script>
<Row cols={{ xs: 2, lg: 3}}>

View File

@@ -73,6 +73,7 @@
userMatch: "contains",
// Filter Modals
cluster: null,
subCluster: null,
partition: null,
states: allJobStates,
shared: "",
@@ -107,6 +108,7 @@
user: filterPresets?.user || "",
userMatch: filterPresets?.userMatch || "contains",
cluster: filterPresets?.cluster || null,
subCluster: filterPresets?.subCluster || null,
partition: filterPresets?.partition || null,
states:
filterPresets?.states || filterPresets?.state
@@ -158,6 +160,7 @@
if (filters.dbId.length != 0)
items.push({ dbId: filters.dbId });
if (filters.cluster) items.push({ cluster: { eq: filters.cluster } });
if (filters.subCluster) items.push({ subCluster: { eq: filters.subCluster } });
if (filters.partition) items.push({ partition: { eq: filters.partition } });
if (filters.states.length != allJobStates?.length)
items.push({ state: filters.states });
@@ -166,12 +169,12 @@
items.push({ project: { [filters.projectMatch]: filters.project } });
if (filters.user)
items.push({ user: { [filters.userMatch]: filters.user } });
if (filters.numNodes.from != null || filters.numNodes.to != null) {
if (filters.numNodes.from != null && filters.numNodes.to != null) {
items.push({
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
});
}
if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) {
if (filters.numAccelerators.from != null && filters.numAccelerators.to != null) {
items.push({
numAccelerators: {
from: filters.numAccelerators.from,
@@ -179,7 +182,7 @@
},
});
}
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) {
if (filters.numHWThreads.from != null && filters.numHWThreads.to != null) {
items.push({
numHWThreads: {
from: filters.numHWThreads.from,
@@ -206,14 +209,21 @@
items.push({ duration: { to: filters.duration.lessThan, from: 0 } });
if (filters.duration.moreThan)
items.push({ duration: { to: 0, from: filters.duration.moreThan } });
if (filters.energy.from != null || filters.energy.to != null)
if (filters.energy.from != null && filters.energy.to != null)
items.push({
energy: { from: filters.energy.from, to: filters.energy.to },
});
if (filters.jobId)
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } });
if (filters.stats.length != 0)
items.push({ metricStats: filters.stats.map((st) => { return { metricName: st.field, range: { from: st.from, to: st.to }} }) });
if (filters.stats.length != 0) {
const metricStats = [];
filters.stats.forEach((st) => {
if (st.from != null && st.to != null)
metricStats.push({ metricName: st.field, range: { from: st.from, to: st.to }});
});
if (metricStats.length != 0)
items.push({metricStats})
};
if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
if (filters.jobName) items.push({ jobName: { contains: filters.jobName } });
if (filters.schedule) items.push({ schedule: filters.schedule });
@@ -260,6 +270,7 @@
opts.push(`userMatch=${filters.userMatch}`);
// Filter Modals
if (filters.cluster) opts.push(`cluster=${filters.cluster}`);
if (filters.subCluster) opts.push(`subCluster=${filters.subCluster}`);
if (filters.partition) opts.push(`partition=${filters.partition}`);
if (filters.states.length != allJobStates?.length)
for (let state of filters.states) opts.push(`state=${state}`);
@@ -280,40 +291,40 @@
opts.push(`duration=morethan-${filters.duration.moreThan}`);
if (filters.tags.length != 0)
for (let tag of filters.tags) opts.push(`tag=${tag}`);
if (filters.numNodes.from > 1 && filters.numNodes.to > 0)
if (filters.numNodes.from > 0 && filters.numNodes.to > 0)
opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`);
else if (filters.numNodes.from > 1 && filters.numNodes.to == 0)
else if (filters.numNodes.from > 0 && filters.numNodes.to == 0)
opts.push(`numNodes=morethan-${filters.numNodes.from}`);
else if (filters.numNodes.from == 1 && filters.numNodes.to > 0)
else if (filters.numNodes.from == 0 && filters.numNodes.to > 0)
opts.push(`numNodes=lessthan-${filters.numNodes.to}`);
if (filters.numHWThreads.from > 1 && filters.numHWThreads.to > 0)
if (filters.numHWThreads.from > 0 && filters.numHWThreads.to > 0)
opts.push(`numHWThreads=${filters.numHWThreads.from}-${filters.numHWThreads.to}`);
else if (filters.numHWThreads.from > 1 && filters.numHWThreads.to == 0)
else if (filters.numHWThreads.from > 0 && filters.numHWThreads.to == 0)
opts.push(`numHWThreads=morethan-${filters.numHWThreads.from}`);
else if (filters.numHWThreads.from == 1 && filters.numHWThreads.to > 0)
else if (filters.numHWThreads.from == 0 && filters.numHWThreads.to > 0)
opts.push(`numHWThreads=lessthan-${filters.numHWThreads.to}`);
if (filters.numAccelerators.from && filters.numAccelerators.to)
if (filters.numAccelerators.from > 0 && filters.numAccelerators.to > 0)
opts.push(`numAccelerators=${filters.numAccelerators.from}-${filters.numAccelerators.to}`);
else if (filters.numAccelerators.from > 1 && filters.numAccelerators.to == 0)
else if (filters.numAccelerators.from > 0 && filters.numAccelerators.to == 0)
opts.push(`numAccelerators=morethan-${filters.numAccelerators.from}`);
else if (filters.numAccelerators.from == 1 && filters.numAccelerators.to > 0)
else if (filters.numAccelerators.from == 0 && filters.numAccelerators.to > 0)
opts.push(`numAccelerators=lessthan-${filters.numAccelerators.to}`);
if (filters.node) opts.push(`node=${filters.node}`);
if (filters.node && filters.nodeMatch != "eq") // "eq" is default-case
opts.push(`nodeMatch=${filters.nodeMatch}`);
if (filters.energy.from > 1 && filters.energy.to > 0)
if (filters.energy.from > 0 && filters.energy.to > 0)
opts.push(`energy=${filters.energy.from}-${filters.energy.to}`);
else if (filters.energy.from > 1 && filters.energy.to == 0)
else if (filters.energy.from > 0 && filters.energy.to == 0)
opts.push(`energy=morethan-${filters.energy.from}`);
else if (filters.energy.from == 1 && filters.energy.to > 0)
else if (filters.energy.from == 0 && filters.energy.to > 0)
opts.push(`energy=lessthan-${filters.energy.to}`);
if (filters.stats.length > 0)
for (let stat of filters.stats) {
if (stat.from > 1 && stat.to > 0)
if (stat.from > 0 && stat.to > 0)
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
else if (stat.from > 1 && stat.to == 0)
else if (stat.from > 0 && stat.to == 0)
opts.push(`stat=${stat.field}-morethan-${stat.from}`);
else if (stat.from == 1 && stat.to > 0)
else if (stat.from == 0 && stat.to > 0)
opts.push(`stat=${stat.field}-lessthan-${stat.to}`);
}
// Build && Return
@@ -339,7 +350,7 @@
{/if}
<DropdownItem header>Manage Filters</DropdownItem>
<DropdownItem onclick={() => (isClusterOpen = true)}>
<Icon name="cpu" /> Cluster/Partition
<Icon name="cpu" /> Cluster/SubCluster/Partition
</DropdownItem>
<DropdownItem onclick={() => (isJobStatesOpen = true)}>
<Icon name="gear-fill" /> Job States
@@ -433,6 +444,9 @@
{#if filters.cluster}
<Info icon="cpu" onclick={() => (isClusterOpen = true)}>
{filters.cluster}
{#if filters.subCluster}
[{filters.subCluster}]
{/if}
{#if filters.partition}
({filters.partition})
{/if}
@@ -511,43 +525,43 @@
</Info>
{/if}
{#if filters.numNodes.from > 1 && filters.numNodes.to > 0}
{#if filters.numNodes.from > 0 && filters.numNodes.to > 0}
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
Nodes: {filters.numNodes.from} - {filters.numNodes.to}
</Info>
{:else if filters.numNodes.from > 1 && filters.numNodes.to == 0}
{:else if filters.numNodes.from > 0 && filters.numNodes.to == 0}
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
&nbsp;&ge;&nbsp;{filters.numNodes.from} Node(s)
</Info>
{:else if filters.numNodes.from == 1 && filters.numNodes.to > 0}
{:else if filters.numNodes.from == 0 && filters.numNodes.to > 0}
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
&nbsp;&le;&nbsp;{filters.numNodes.to} Node(s)
</Info>
{/if}
{#if filters.numHWThreads.from > 1 && filters.numHWThreads.to > 0}
{#if filters.numHWThreads.from > 0 && filters.numHWThreads.to > 0}
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
HWThreads: {filters.numHWThreads.from} - {filters.numHWThreads.to}
</Info>
{:else if filters.numHWThreads.from > 1 && filters.numHWThreads.to == 0}
{:else if filters.numHWThreads.from > 0 && filters.numHWThreads.to == 0}
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
&nbsp;&ge;&nbsp;{filters.numHWThreads.from} HWThread(s)
</Info>
{:else if filters.numHWThreads.from == 1 && filters.numHWThreads.to > 0}
{:else if filters.numHWThreads.from == 0 && filters.numHWThreads.to > 0}
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
&nbsp;&le;&nbsp;{filters.numHWThreads.to} HWThread(s)
</Info>
{/if}
{#if filters.numAccelerators.from > 1 && filters.numAccelerators.to > 0}
{#if filters.numAccelerators.from > 0 && filters.numAccelerators.to > 0}
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to}
</Info>
{:else if filters.numAccelerators.from > 1 && filters.numAccelerators.to == 0}
{:else if filters.numAccelerators.from > 0 && filters.numAccelerators.to == 0}
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
&nbsp;&ge;&nbsp;{filters.numAccelerators.from} Acc(s)
</Info>
{:else if filters.numAccelerators.from == 1 && filters.numAccelerators.to > 0}
{:else if filters.numAccelerators.from == 0 && filters.numAccelerators.to > 0}
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
&nbsp;&le;&nbsp;{filters.numAccelerators.to} Acc(s)
</Info>
@@ -559,15 +573,15 @@
</Info>
{/if}
{#if filters.energy.from > 1 && filters.energy.to > 0}
{#if filters.energy.from > 0 && filters.energy.to > 0}
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
Total Energy: {filters.energy.from} - {filters.energy.to} kWh
</Info>
{:else if filters.energy.from > 1 && filters.energy.to == 0}
{:else if filters.energy.from > 0 && filters.energy.to == 0}
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
Total Energy &ge;&nbsp;{filters.energy.from} kWh
</Info>
{:else if filters.energy.from == 1 && filters.energy.to > 0}
{:else if filters.energy.from == 0 && filters.energy.to > 0}
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
Total Energy &le;&nbsp;{filters.energy.to} kWh
</Info>
@@ -575,15 +589,15 @@
{#if filters.stats.length > 0}
{#each filters.stats as stat}
{#if stat.from > 1 && stat.to > 0}
{#if stat.from > 0 && stat.to > 0}
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
{stat.field}: {stat.from} - {stat.to} {stat.unit}
</Info>&thinsp;
{:else if stat.from > 1 && stat.to == 0}
{:else if stat.from > 0 && stat.to == 0}
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
{stat.field} &ge;&nbsp;{stat.from} {stat.unit}
</Info>&thinsp;
{:else if stat.from == 1 && stat.to > 0}
{:else if stat.from == 0 && stat.to > 0}
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
{stat.field} &le;&nbsp;{stat.to} {stat.unit}
</Info>&thinsp;
@@ -596,6 +610,7 @@
bind:isOpen={isClusterOpen}
presetCluster={filters.cluster}
presetPartition={filters.partition}
presetSubCluster={filters.subCluster}
{disableClusterSelection}
setFilter={(filter) => updateFilters(filter)}
/>

View File

@@ -1,10 +1,11 @@
<!--
@component Filter sub-component for selecting cluster and subCluster
@component Filter sub-component for selecting cluster, partition and subCluster
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetCluster String?`: The latest selected cluster [Default: ""]
- `presetPartition String?`: The latest selected partition [Default: ""]
- `presetSubCluster String?`: The latest selected subCluster [Default: ""]
- `disableClusterSelection Bool?`: Is the selection disabled [Default: false]
- `setFilter Func`: The callback function to apply current filter selection
-->
@@ -26,6 +27,7 @@
isOpen = $bindable(false),
presetCluster = "",
presetPartition = "",
presetSubCluster = "",
disableClusterSelection = false,
setFilter
} = $props();
@@ -36,10 +38,11 @@
const clusterInfos = $derived($initialized ? getContext("clusters") : null);
let pendingCluster = $derived(presetCluster);
let pendingPartition = $derived(presetPartition);
let pendingSubCluster = $derived(presetSubCluster);
</script>
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
<ModalHeader>Select Cluster & Slurm Partition</ModalHeader>
<ModalHeader>Select Cluster, SubCluster & Partition</ModalHeader>
<ModalBody>
{#if $initialized}
<h4>Cluster</h4>
@@ -51,7 +54,7 @@
<ListGroupItem
disabled={disableClusterSelection}
active={pendingCluster == null}
onclick={() => ((pendingCluster = null), (pendingPartition = null))}
onclick={() => ((pendingCluster = null), (pendingPartition = null), (pendingSubCluster = null))}
>
Any Cluster
</ListGroupItem>
@@ -60,7 +63,7 @@
disabled={disableClusterSelection}
active={pendingCluster == cluster.name}
onclick={() => (
(pendingCluster = cluster.name), (pendingPartition = null)
(pendingCluster = cluster.name), (pendingPartition = null), (pendingSubCluster = null)
)}
>
{cluster.name}
@@ -71,7 +74,27 @@
{/if}
{#if $initialized && pendingCluster != null}
<br />
<h4>Partiton</h4>
<h4>SubCluster</h4>
<ListGroup>
<ListGroupItem
active={pendingSubCluster == null}
onclick={() => (pendingSubCluster = null)}
>
Any SubCluster
</ListGroupItem>
{#each clusterInfos?.find((c) => c.name == pendingCluster)?.subClusters as subCluster}
<ListGroupItem
active={pendingSubCluster == subCluster.name}
onclick={() => (pendingSubCluster = subCluster.name)}
>
{subCluster.name}
</ListGroupItem>
{/each}
</ListGroup>
{/if}
{#if $initialized && pendingCluster != null}
<br />
<h4>Partition</h4>
<ListGroup>
<ListGroupItem
active={pendingPartition == null}
@@ -95,7 +118,7 @@
color="primary"
onclick={() => {
isOpen = false;
setFilter({ cluster: pendingCluster, partition: pendingPartition });
setFilter({ cluster: pendingCluster, subCluster: pendingSubCluster, partition: pendingPartition });
}}>Close & Apply</Button
>
{#if !disableClusterSelection}
@@ -105,7 +128,8 @@
isOpen = false;
pendingCluster = null;
pendingPartition = null;
setFilter({ cluster: pendingCluster, partition: pendingPartition})
pendingSubCluster = null;
setFilter({ cluster: pendingCluster, subCluster: pendingSubCluster, partition: pendingPartition })
}}>Reset</Button
>
{/if}

View File

@@ -28,31 +28,29 @@
} = $props();
/* Const */
const minEnergyPreset = 1;
const minEnergyPreset = 0;
const maxEnergyPreset = 100;
/* Derived */
// Pending
let pendingEnergyState = $derived({
from: presetEnergy?.from ? presetEnergy.from : minEnergyPreset,
to: !(presetEnergy.to == null || presetEnergy.to == 0) ? presetEnergy.to : maxEnergyPreset,
from: presetEnergy?.from || minEnergyPreset,
to: (presetEnergy.to == 0) ? null : presetEnergy.to,
});
// Changable
let energyState = $derived({
from: presetEnergy?.from ? presetEnergy.from : minEnergyPreset,
to: !(presetEnergy.to == null || presetEnergy.to == 0) ? presetEnergy.to : maxEnergyPreset,
from: presetEnergy?.from || minEnergyPreset,
to: (presetEnergy.to == 0) ? null : presetEnergy.to,
});
const energyActive = $derived(!(JSON.stringify(energyState) === JSON.stringify({ from: minEnergyPreset, to: maxEnergyPreset })));
// Block Apply if null
const disableApply = $derived(energyState.from === null || energyState.to === null);
const energyActive = $derived(!(JSON.stringify(energyState) === JSON.stringify({ from: minEnergyPreset, to: null })));
/* Function */
function setEnergy() {
if (energyActive) {
pendingEnergyState = {
from: energyState.from,
to: (energyState.to == maxEnergyPreset) ? 0 : energyState.to
from: (!energyState?.from) ? 0 : energyState.from,
to: (energyState.to === null) ? 0 : energyState.to
};
} else {
pendingEnergyState = { from: null, to: null};
@@ -86,7 +84,6 @@
<ModalFooter>
<Button
color="primary"
disabled={disableApply}
onclick={() => {
isOpen = false;
setEnergy();

View File

@@ -98,44 +98,38 @@
// Pending
let pendingNumNodes = $derived({
from: presetNumNodes.from,
to: (presetNumNodes.to == 0) ? maxNumNodes : presetNumNodes.to
to: (presetNumNodes.to == 0) ? null : presetNumNodes.to
});
let pendingNumHWThreads = $derived({
from: presetNumHWThreads.from,
to: (presetNumHWThreads.to == 0) ? maxNumHWThreads : presetNumHWThreads.to
to: (presetNumHWThreads.to == 0) ? null : presetNumHWThreads.to
});
let pendingNumAccelerators = $derived({
from: presetNumAccelerators.from,
to: (presetNumAccelerators.to == 0) ? maxNumAccelerators : presetNumAccelerators.to
to: (presetNumAccelerators.to == 0) ? null : presetNumAccelerators.to
});
let pendingNamedNode = $derived(presetNamedNode);
let pendingNodeMatch = $derived(presetNodeMatch);
// Changable States
let nodesState = $derived({
from: presetNumNodes.from,
to: (presetNumNodes.to == 0) ? maxNumNodes : presetNumNodes.to
from: presetNumNodes?.from || 0,
to: (presetNumNodes.to == 0) ? null : presetNumNodes.to
});
let threadState = $derived({
from: presetNumHWThreads.from,
to: (presetNumHWThreads.to == 0) ? maxNumHWThreads : presetNumHWThreads.to
from: presetNumHWThreads?.from || 0,
to: (presetNumHWThreads.to == 0) ? null : presetNumHWThreads.to
});
let accState = $derived({
from: presetNumAccelerators.from,
to: (presetNumAccelerators.to == 0) ? maxNumAccelerators : presetNumAccelerators.to
from: presetNumAccelerators?.from || 0,
to: (presetNumAccelerators.to == 0) ? null : presetNumAccelerators.to
});
const initialized = $derived(getContext("initialized") || false);
const clusterInfos = $derived($initialized ? getContext("clusters") : null);
// Is Selection Active
const nodesActive = $derived(!(JSON.stringify(nodesState) === JSON.stringify({ from: 1, to: maxNumNodes })));
const threadActive = $derived(!(JSON.stringify(threadState) === JSON.stringify({ from: 1, to: maxNumHWThreads })));
const accActive = $derived(!(JSON.stringify(accState) === JSON.stringify({ from: 1, to: maxNumAccelerators })));
// Block Apply if null
const disableApply = $derived(
nodesState.from === null || nodesState.to === null ||
threadState.from === null || threadState.to === null ||
accState.from === null || accState.to === null
);
const nodesActive = $derived(!(JSON.stringify(nodesState) === JSON.stringify({ from: 0, to: null })));
const threadActive = $derived(!(JSON.stringify(threadState) === JSON.stringify({ from: 0, to: null })));
const accActive = $derived(!(JSON.stringify(accState) === JSON.stringify({ from: 0, to: null })));
/* Reactive Effects | Svelte 5 onMount */
$effect(() => {
@@ -153,58 +147,28 @@
}
});
$effect(() => {
if (
$initialized &&
pendingNumNodes.from == null &&
pendingNumNodes.to == null
) {
nodesState = { from: 1, to: maxNumNodes };
}
});
$effect(() => {
if (
$initialized &&
pendingNumHWThreads.from == null &&
pendingNumHWThreads.to == null
) {
threadState = { from: 1, to: maxNumHWThreads };
}
});
$effect(() => {
if (
$initialized &&
pendingNumAccelerators.from == null &&
pendingNumAccelerators.to == null
) {
accState = { from: 1, to: maxNumAccelerators };
}
});
/* Functions */
function setResources() {
if (nodesActive) {
pendingNumNodes = {
from: nodesState.from,
to: (nodesState.to == maxNumNodes) ? 0 : nodesState.to
from: (!nodesState?.from) ? 0 : nodesState.from,
to: (nodesState.to === null) ? 0 : nodesState.to
};
} else {
pendingNumNodes = { from: null, to: null};
};
if (threadActive) {
pendingNumHWThreads = {
from: threadState.from,
to: (threadState.to == maxNumHWThreads) ? 0 : threadState.to
from: (!threadState?.from) ? 0 : threadState.from,
to: (threadState.to === null) ? 0 : threadState.to
};
} else {
pendingNumHWThreads = { from: null, to: null};
};
if (accActive) {
pendingNumAccelerators = {
from: accState.from,
to: (accState.to == maxNumAccelerators) ? 0 : accState.to
from: (!accState?.from) ? 0 : accState.from,
to: (accState.to === null) ? 0 : accState.to
};
} else {
pendingNumAccelerators = { from: null, to: null};
@@ -249,7 +213,7 @@
nodesState.from = detail[0];
nodesState.to = detail[1];
}}
sliderMin={1}
sliderMin={0}
sliderMax={maxNumNodes}
fromPreset={nodesState.from}
toPreset={nodesState.to}
@@ -269,7 +233,7 @@
threadState.from = detail[0];
threadState.to = detail[1];
}}
sliderMin={1}
sliderMin={0}
sliderMax={maxNumHWThreads}
fromPreset={threadState.from}
toPreset={threadState.to}
@@ -289,7 +253,7 @@
accState.from = detail[0];
accState.to = detail[1];
}}
sliderMin={1}
sliderMin={0}
sliderMax={maxNumAccelerators}
fromPreset={accState.from}
toPreset={accState.to}
@@ -300,7 +264,6 @@
<ModalFooter>
<Button
color="primary"
disabled={disableApply}
onclick={() => {
isOpen = false;
setResources();

View File

@@ -34,7 +34,8 @@
function setRanges() {
for (let as of availableStats) {
if (as.enabled) {
as.to = (as.to == as.peak) ? 0 : as.to
as.from = (!as?.from) ? 0 : as.from,
as.to = (as.to == null) ? 0 : as.to
}
};
}
@@ -42,8 +43,8 @@
function resetRanges() {
for (let as of availableStats) {
as.enabled = false
as.from = 1
as.to = as.peak
as.from = null
as.to = null
};
}
</script>
@@ -66,13 +67,13 @@
changeRange={(detail) => {
aStat.from = detail[0];
aStat.to = detail[1];
if (aStat.from == 1 && aStat.to == aStat.peak) {
if (aStat.from == 0 && aStat.to === null) {
aStat.enabled = false;
} else {
aStat.enabled = true;
}
}}
sliderMin={1}
sliderMin={0}
sliderMax={aStat.peak}
fromPreset={aStat.from}
toPreset={aStat.to}

View File

@@ -228,7 +228,7 @@
{/each}
{#if job?.metaData?.message}
<hr class="mt-1 mb-2" />
{@html job.metaData.message}
<span style="white-space: pre-wrap">{job.metaData.message}</span>
{/if}
</CardBody>
</Card>

View File

@@ -287,12 +287,12 @@
} else if (nodesData[i]?.schedulerState == "allocated") {
//u.ctx.strokeStyle = "rgb(0, 255, 0)";
u.ctx.fillStyle = "rgba(0, 255, 0, 0.5)";
} else if (nodesData[i]?.schedulerState == "notindb") {
} else if (nodesData[i]?.schedulerState == "mixed") {
//u.ctx.strokeStyle = "rgb(0, 0, 0)";
u.ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
} else { // Fallback: All other DEFINED states
//u.ctx.strokeStyle = "rgb(255, 0, 0)";
u.ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
} else { // Fallback: All other states: Reserved, Down, Notindb
//u.ctx.strokeStyle = "rgb(255, 0, 0)";
u.ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
}
}
} else {
@@ -450,10 +450,10 @@
tooltip.style.borderColor = "rgb(0, 0, 255)";
} else if (nodesData[i]?.schedulerState == "allocated") {
tooltip.style.borderColor = "rgb(0, 255, 0)";
} else if (nodesData[i]?.schedulerState == "notindb") { // Missing from DB table
tooltip.style.borderColor = "rgb(0, 0, 0)";
} else { // Fallback: All other DEFINED states
} else if (nodesData[i]?.schedulerState == "mixed") {
tooltip.style.borderColor = "rgb(255, 0, 0)";
} else { // Fallback: All other DEFINED states
tooltip.style.borderColor = "rgb(0, 0, 0)";
}
}
} else {
@@ -904,7 +904,7 @@
if (jobsData) {
const posX = u.valToPos(0.1, "x", true)
const posXLimit = u.valToPos(100, "x", true)
const posY = u.valToPos(17500.0, "y", true)
const posY = 7 // u.valToPos(17500.0, "y", true)
u.ctx.fillStyle = 'black'
u.ctx.fillText('0 Hours', posX, posY)
const start = posX + 10
@@ -921,16 +921,16 @@
// Nodes: The Colors Of NodeStates
if (nodesData) {
const posY = u.valToPos(17500.0, "y", true)
const posY = 7 // u.valToPos(17500.0, "y", true)
const posAllocDot = u.valToPos(0.03, "x", true)
const posAllocText = posAllocDot + 60
const posIdleDot = u.valToPos(0.3, "x", true)
const posIdleText = posIdleDot + 30
const posOtherDot = u.valToPos(3, "x", true)
const posIdleDot = u.valToPos(1, "x", true)
const posIdleText = posIdleDot + 28
const posMixedDot = u.valToPos(7, "x", true)
const posMixedText = posMixedDot + 40
const posOtherDot = u.valToPos(100, "x", true)
const posOtherText = posOtherDot + 40
const posMissingDot = u.valToPos(30, "x", true)
const posMissingText = posMissingDot + 80
u.ctx.fillStyle = "rgb(0, 255, 0)"
u.ctx.beginPath()
@@ -948,16 +948,16 @@
u.ctx.fillStyle = "rgb(255, 0, 0)"
u.ctx.beginPath()
u.ctx.arc(posOtherDot, posY, 3, 0, Math.PI * 2, false)
u.ctx.arc(posMixedDot, posY, 3, 0, Math.PI * 2, false)
u.ctx.fill()
u.ctx.fillStyle = 'black'
u.ctx.fillText('Other', posOtherText, posY)
u.ctx.fillText('Mixed', posMixedText, posY)
u.ctx.fillStyle = 'black'
u.ctx.beginPath()
u.ctx.arc(posMissingDot, posY, 3, 0, Math.PI * 2, false)
u.ctx.arc(posOtherDot, posY, 3, 0, Math.PI * 2, false)
u.ctx.fill()
u.ctx.fillText('Missing in DB', posMissingText, posY)
u.ctx.fillText('Other', posOtherText, posY)
}
}
},

View File

@@ -338,7 +338,7 @@
// The Color Scale For Time Information
const posX = u.valToPos(0.1, "x", true)
const posXLimit = u.valToPos(100, "x", true)
const posY = u.valToPos(14000.0, "y", true)
const posY = 7 // u.valToPos(((subCluster?.flopRateSimd?.value || 10000) + 5000), "y", true)
u.ctx.fillStyle = 'black'
u.ctx.fillText('Start', posX, posY)
const start = posX + 10

View File

@@ -21,7 +21,7 @@
let {
sliderMin,
sliderMax,
fromPreset = 1,
fromPreset = 0,
toPreset = 100,
changeRange
} = $props();
@@ -33,9 +33,9 @@
/* Derived */
let pendingValues = $derived([fromPreset, toPreset]);
let sliderFrom = $derived(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
let sliderTo = $derived(Math.min(((toPreset == null ? sliderMin : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
let inputFieldFrom = $derived(fromPreset ? fromPreset.toString() : null);
let inputFieldTo = $derived(toPreset ? toPreset.toString() : null);
let sliderTo = $derived(Math.min(((toPreset == null ? sliderMax : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
let inputFieldFrom = $derived(fromPreset != null ? fromPreset.toString() : null);
let inputFieldTo = $derived(toPreset != null ? toPreset.toString() : null);
/* Var Init */
let timeoutId = null;
@@ -79,17 +79,22 @@
evt.preventDefault()
evt.stopPropagation()
const newV = Number.parseInt(evt.target.value);
const newP = clamp((newV - sliderMin) / (sliderMax - sliderMin), 0., 1.)
const newP = clamp((newV - sliderMin) / (sliderMax - sliderMin), 0., 1., target)
updateStates(newV, newP, target);
}
function clamp(x, testMin, testMax) {
return x < testMin
? testMin
: (x > testMax
? testMax
: x
);
function clamp(x, testMin, testMax, target) {
if (isNaN(x)) {
if (target == 'from') return testMin
else if (target == 'to') return testMax
} else {
return x < testMin
? testMin
: (x > testMax
? testMax
: x
);
}
}
function draggable(node) {
@@ -159,23 +164,23 @@
<div class="double-range-container">
<div class="header">
<input class="form-control" type="text" placeholder="from..." value={inputFieldFrom}
<input class="form-control" type="text" placeholder={`${sliderMin} ...`} value={inputFieldFrom}
oninput={(e) => {
inputChanged(e, 'from');
}}
/>
{#if inputFieldFrom != sliderMin?.toString() && inputFieldTo != sliderMax?.toString() }
{#if (inputFieldFrom && inputFieldFrom != sliderMin?.toString()) && inputFieldTo != null }
<span>Selected: Range <b> {inputFieldFrom} </b> - <b> {inputFieldTo} </b></span>
{:else if inputFieldFrom != sliderMin?.toString() && inputFieldTo == sliderMax?.toString() }
<span>Selected: More than <b> {inputFieldFrom} </b> </span>
{:else if inputFieldFrom == sliderMin?.toString() && inputFieldTo != sliderMax?.toString() }
<span>Selected: Less than <b> {inputFieldTo} </b></span>
{:else if (inputFieldFrom && inputFieldFrom != sliderMin?.toString()) && inputFieldTo == null }
<span>Selected: More Than Equal <b> {inputFieldFrom} </b> </span>
{:else if (!inputFieldFrom || inputFieldFrom == sliderMin?.toString()) && inputFieldTo != null }
<span>Selected: Less Than Equal <b> {inputFieldTo} </b></span>
{:else}
<span><i>No Selection</i></span>
{/if}
<input class="form-control" type="text" placeholder="to..." value={inputFieldTo}
<input class="form-control" type="text" placeholder={`... ${sliderMax} ...`} value={inputFieldTo}
oninput={(e) => {
inputChanged(e, 'to');
}}

View File

@@ -347,8 +347,8 @@ export function getStatsItems(presetStats = []) {
field: presetEntry.field,
text: `${gm.name} (${gm.footprint})`,
metric: gm.name,
from: presetEntry.from,
to: (presetEntry.to == 0) ? mc.peak : presetEntry.to,
from: presetEntry?.from || 0,
to: (presetEntry.to == 0) ? null : presetEntry.to,
peak: mc.peak,
enabled: true,
unit: `${gm?.unit?.prefix ? gm.unit.prefix : ''}${gm.unit.base}`
@@ -358,8 +358,8 @@ export function getStatsItems(presetStats = []) {
field: `${gm.name}_${gm.footprint}`,
text: `${gm.name} (${gm.footprint})`,
metric: gm.name,
from: 1,
to: mc.peak,
from: 0,
to: null,
peak: mc.peak,
enabled: false,
unit: `${gm?.unit?.prefix ? gm.unit.prefix : ''}${gm.unit.base}`

View File

@@ -87,7 +87,7 @@
{#if subClusters?.length > 1}
{#each subClusters.map(sc => sc.name) as scn}
<TabPane tabId="{scn}-usage-dash" tab="{scn.charAt(0).toUpperCase() + scn.slice(1)} Usage">
<TabPane tabId="{scn}-usage-dash" tab="{scn} Usage">
<CardBody>
<UsageDash {presetCluster} presetSubCluster={scn} {useCbColors} loadMe={(activeTab === `${scn}-usage-dash`)}></UsageDash>
</CardBody>

View File

@@ -64,7 +64,7 @@
const filter = $derived([
{ cluster: { eq: cluster } },
{ state: ["running"] },
{ node: { contains: nodeData.host } },
{ node: { eq: nodeData.host } },
]);
const nodeJobsData = $derived(queryStore({
client: client,