Compare commits

..

2 Commits

Author SHA1 Message Date
6397f1eaae Merge branch 'release/v1.5' into feature/add_subcluster_filter 2026-04-24 11:55:28 +02:00
Christoph Kluge
4c59aee304 feat: add subCluster filter to filter component 2026-04-24 11:43:42 +02:00
5 changed files with 45 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
TARGET = ./cc-backend
FRONTEND = ./web/frontend
VERSION = 1.5.4
VERSION = 1.5.3
GIT_HASH := $(shell git rev-parse --short HEAD || echo 'development')
CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S")
LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}'

View File

@@ -1,4 +1,4 @@
# `cc-backend` version 1.5.4
# `cc-backend` version 1.5.3
Supports job archive version 3 and database version 11.
@@ -15,26 +15,6 @@ While we are confident that the memory issue with the metricstore cleanup move
policy is fixed, it is still recommended to use delete policy for cleanup.
This is also the default.
## Changes in 1.5.4
### Bug fixes
- **Roofline legend placement**: Roofline plot legends now use fixed
coordinates instead of dynamic placement, preventing the legend from
overlapping data points or being rendered off-canvas (#546).
- **Subcluster usage tab labels**: Subcluster names in the status dashboard
usage tabs are no longer force-capitalized; the original cluster-defined
casing is preserved.
- **NodeListRow host filter**: The running-jobs query in the node list row now
filters by exact node hostname (`eq`) instead of substring match
(`contains`), avoiding incorrect matches when one hostname is a prefix of
another.
### Dependencies
- **Go module upgrades**: Refreshed Go module dependencies to their latest
compatible versions.
## Changes in 1.5.3
### Bug fixes

View File

@@ -238,6 +238,9 @@ func buildFilterPresets(query url.Values) map[string]any {
if query.Get("cluster") != "" {
filterPresets["cluster"] = query.Get("cluster")
}
if query.Get("subCluster") != "" {
filterPresets["subCluster"] = query.Get("subCluster")
}
if query.Get("partition") != "" {
filterPresets["partition"] = query.Get("partition")
}

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 });
@@ -267,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}`);
@@ -346,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
@@ -440,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}
@@ -603,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}