mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-06-08 08:33:49 +02:00
Migrate job list view and filter components
- filters now inactive in user jobs, lists and analysis due to missing dispatch
This commit is contained in:
parent
3f1768e467
commit
ffd596e2c7
@ -8,7 +8,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from "svelte";
|
import { untrack, onMount, getContext } from "svelte";
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
@ -28,42 +28,57 @@
|
|||||||
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
||||||
|
|
||||||
const { query: initq } = init();
|
const { query: initq } = init();
|
||||||
|
|
||||||
const ccconfig = getContext("cc-config");
|
const ccconfig = getContext("cc-config");
|
||||||
|
|
||||||
export let filterPresets = {};
|
// Svelte 5 Props
|
||||||
export let authlevel;
|
let { filterPresets, authlevel, roles } = $props();
|
||||||
export let roles;
|
|
||||||
|
|
||||||
let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
// Svelte 5 Reactive Vars
|
||||||
let filterBuffer = [];
|
let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
||||||
let selectedJobs = [];
|
let selectedJobs = $state([]);
|
||||||
let jobList,
|
let filterBuffer = $state([]);
|
||||||
jobCompare,
|
let jobList = $state(null);
|
||||||
matchedListJobs,
|
let jobCompare = $state(null);
|
||||||
matchedCompareJobs = null;
|
let matchedListJobs = $state(0);
|
||||||
let sorting = { field: "startTime", type: "col", order: "DESC" },
|
let matchedCompareJobs = $state(0);
|
||||||
isSortingOpen = false,
|
let isSortingOpen = $state(false);
|
||||||
isMetricsSelectionOpen = false;
|
let showCompare = $state(false);
|
||||||
let metrics = filterPresets.cluster
|
let isMetricsSelectionOpen = $state(false);
|
||||||
|
let sorting = $state({ field: "startTime", type: "col", order: "DESC" });
|
||||||
|
let selectedCluster = $state(filterPresets?.cluster ? filterPresets.cluster : null);
|
||||||
|
let metrics = $state(filterPresets.cluster
|
||||||
? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] ||
|
? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] ||
|
||||||
ccconfig.plot_list_selectedMetrics
|
ccconfig.plot_list_selectedMetrics
|
||||||
: ccconfig.plot_list_selectedMetrics;
|
: ccconfig.plot_list_selectedMetrics
|
||||||
let showFootprint = filterPresets.cluster
|
);
|
||||||
|
let showFootprint = $state(filterPresets.cluster
|
||||||
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
|
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
|
||||||
: !!ccconfig.plot_list_showFootprint;
|
: !!ccconfig.plot_list_showFootprint
|
||||||
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null;
|
);
|
||||||
|
|
||||||
|
// Classic Inits
|
||||||
let presetProject = filterPresets?.project ? filterPresets.project : ""
|
let presetProject = filterPresets?.project ? filterPresets.project : ""
|
||||||
let showCompare = false;
|
|
||||||
|
|
||||||
// The filterPresets are handled by the Filters component,
|
// The filterPresets are handled by the Filters component,
|
||||||
// so we need to wait for it to be ready before we can start a query.
|
// so we need to wait for it to be ready before we can start a query.
|
||||||
// This is also why JobList component starts out with a paused query.
|
// This is also why JobList component starts out with a paused query.
|
||||||
onMount(() => filterComponent.updateFilters());
|
onMount(() => filterComponent.updateFilters());
|
||||||
|
|
||||||
$: if (filterComponent && selectedJobs.length == 0) {
|
function resetJobSelection() {
|
||||||
filterComponent.updateFilters({dbId: []})
|
if (filterComponent && selectedJobs.length === 0) {
|
||||||
}
|
filterComponent.updateFilters({ dbId: [] });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
// Reactive : Trigger Effect
|
||||||
|
selectedJobs.length
|
||||||
|
untrack(() => {
|
||||||
|
// Unreactive : Apply Reset w/o starting infinite loop
|
||||||
|
resetJobSelection()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- ROW1: Status-->
|
<!-- ROW1: Status-->
|
||||||
@ -85,13 +100,13 @@
|
|||||||
<Row cols={{ xs: 1, md: 2, lg: 5}} class="mb-3">
|
<Row cols={{ xs: 1, md: 2, lg: 5}} class="mb-3">
|
||||||
<Col lg="2" class="mb-2 mb-lg-0">
|
<Col lg="2" class="mb-2 mb-lg-0">
|
||||||
<ButtonGroup class="w-100">
|
<ButtonGroup class="w-100">
|
||||||
<Button outline color="primary" on:click={() => (isSortingOpen = true)} disabled={showCompare}>
|
<Button outline color="primary" onclick={() => (isSortingOpen = true)} disabled={showCompare}>
|
||||||
<Icon name="sort-up" /> Sorting
|
<Icon name="sort-up" /> Sorting
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => (isMetricsSelectionOpen = true)}
|
onclick={() => (isMetricsSelectionOpen = true)}
|
||||||
>
|
>
|
||||||
<Icon name="graph-up" /> Metrics
|
<Icon name="graph-up" /> Metrics
|
||||||
</Button>
|
</Button>
|
||||||
@ -99,11 +114,11 @@
|
|||||||
</Col>
|
</Col>
|
||||||
<Col lg="4" class="mb-1 mb-lg-0">
|
<Col lg="4" class="mb-1 mb-lg-0">
|
||||||
<Filters
|
<Filters
|
||||||
showFilter={!showCompare}
|
|
||||||
{filterPresets}
|
|
||||||
matchedJobs={showCompare? matchedCompareJobs: matchedListJobs}
|
|
||||||
bind:this={filterComponent}
|
bind:this={filterComponent}
|
||||||
on:update-filters={({ detail }) => {
|
{filterPresets}
|
||||||
|
showFilter={!showCompare}
|
||||||
|
matchedJobs={showCompare? matchedCompareJobs: matchedListJobs}
|
||||||
|
applyFilters={(detail) => {
|
||||||
selectedCluster = detail.filters[0]?.cluster
|
selectedCluster = detail.filters[0]?.cluster
|
||||||
? detail.filters[0].cluster.eq
|
? detail.filters[0].cluster.eq
|
||||||
: null;
|
: null;
|
||||||
@ -122,13 +137,13 @@
|
|||||||
{presetProject}
|
{presetProject}
|
||||||
bind:authlevel
|
bind:authlevel
|
||||||
bind:roles
|
bind:roles
|
||||||
on:set-filter={({ detail }) => filterComponent.updateFilters(detail)}
|
setFilter={(filter) => filterComponent.updateFilters(filter)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
<Col lg="2" class="mb-1 mb-lg-0">
|
<Col lg="2" class="mb-1 mb-lg-0">
|
||||||
{#if !showCompare}
|
{#if !showCompare}
|
||||||
<Refresher on:refresh={() => {
|
<Refresher onRefresh={() => {
|
||||||
jobList.refreshJobs()
|
jobList.refreshJobs()
|
||||||
jobList.refreshAllMetrics()
|
jobList.refreshAllMetrics()
|
||||||
}} />
|
}} />
|
||||||
@ -136,7 +151,7 @@
|
|||||||
</Col>
|
</Col>
|
||||||
<Col lg="2" class="mb-2 mb-lg-0">
|
<Col lg="2" class="mb-2 mb-lg-0">
|
||||||
<ButtonGroup class="w-100">
|
<ButtonGroup class="w-100">
|
||||||
<Button color="primary" disabled={matchedListJobs >= 500 && !(selectedJobs.length != 0)} on:click={() => {
|
<Button color="primary" disabled={matchedListJobs >= 500 && !(selectedJobs.length != 0)} onclick={() => {
|
||||||
if (selectedJobs.length != 0) filterComponent.updateFilters({dbId: selectedJobs}, true)
|
if (selectedJobs.length != 0) filterComponent.updateFilters({dbId: selectedJobs}, true)
|
||||||
showCompare = !showCompare
|
showCompare = !showCompare
|
||||||
}} >
|
}} >
|
||||||
@ -144,7 +159,7 @@
|
|||||||
'Compare Jobs' + (selectedJobs.length != 0 ? ` (${selectedJobs.length} selected)` : matchedListJobs >= 500 ? ` (Too Many)` : ``)}
|
'Compare Jobs' + (selectedJobs.length != 0 ? ` (${selectedJobs.length} selected)` : matchedListJobs >= 500 ? ` (Too Many)` : ``)}
|
||||||
</Button>
|
</Button>
|
||||||
{#if !showCompare && selectedJobs.length != 0}
|
{#if !showCompare && selectedJobs.length != 0}
|
||||||
<Button color="warning" on:click={() => {
|
<Button color="warning" onclick={() => {
|
||||||
selectedJobs = [] // Only empty array, filters handled by reactive reset
|
selectedJobs = [] // Only empty array, filters handled by reactive reset
|
||||||
}}>
|
}}>
|
||||||
Clear
|
Clear
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import {
|
import {
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -37,14 +36,16 @@
|
|||||||
import Resources from "./filters/Resources.svelte";
|
import Resources from "./filters/Resources.svelte";
|
||||||
import Statistics from "./filters/Stats.svelte";
|
import Statistics from "./filters/Stats.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
// Svelte 5 Props
|
||||||
|
let {
|
||||||
export let menuText = null;
|
menuText = null,
|
||||||
export let filterPresets = {};
|
filterPresets = {},
|
||||||
export let disableClusterSelection = false;
|
disableClusterSelection = false,
|
||||||
export let startTimeQuickSelect = false;
|
startTimeQuickSelect = false,
|
||||||
export let matchedJobs = -2;
|
matchedJobs = -2,
|
||||||
export let showFilter = true;
|
showFilter = true,
|
||||||
|
applyFilters
|
||||||
|
} = $props();
|
||||||
|
|
||||||
const startTimeSelectOptions = [
|
const startTimeSelectOptions = [
|
||||||
{ range: "", rangeLabel: "No Selection"},
|
{ range: "", rangeLabel: "No Selection"},
|
||||||
@ -92,7 +93,8 @@
|
|||||||
stats: [],
|
stats: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
let filters = {
|
// Svelte 5 Reactive Vars
|
||||||
|
let filters = $state({
|
||||||
projectMatch: filterPresets.projectMatch || "contains",
|
projectMatch: filterPresets.projectMatch || "contains",
|
||||||
userMatch: filterPresets.userMatch || "contains",
|
userMatch: filterPresets.userMatch || "contains",
|
||||||
jobIdMatch: filterPresets.jobIdMatch || "eq",
|
jobIdMatch: filterPresets.jobIdMatch || "eq",
|
||||||
@ -126,19 +128,19 @@
|
|||||||
numAccelerators: filterPresets.numAccelerators || { from: null, to: null },
|
numAccelerators: filterPresets.numAccelerators || { from: null, to: null },
|
||||||
|
|
||||||
stats: filterPresets.stats || [],
|
stats: filterPresets.stats || [],
|
||||||
};
|
});
|
||||||
|
|
||||||
let isClusterOpen = false,
|
let isClusterOpen = $state(false)
|
||||||
isJobStatesOpen = false,
|
let isJobStatesOpen = $state(false)
|
||||||
isStartTimeOpen = false,
|
let isStartTimeOpen = $state(false)
|
||||||
isTagsOpen = false,
|
let isTagsOpen = $state(false)
|
||||||
isDurationOpen = false,
|
let isDurationOpen = $state(false)
|
||||||
isEnergyOpen = false,
|
let isEnergyOpen = $state(false)
|
||||||
isResourcesOpen = false,
|
let isResourcesOpen = $state(false)
|
||||||
isStatsOpen = false,
|
let isStatsOpen = $state(false)
|
||||||
isNodesModified = false,
|
let isNodesModified = $state(false)
|
||||||
isHwthreadsModified = false,
|
let isHwthreadsModified = $state(false)
|
||||||
isAccsModified = false;
|
let isAccsModified = $state(false)
|
||||||
|
|
||||||
// Can be called from the outside to trigger a 'update' event from this component.
|
// Can be called from the outside to trigger a 'update' event from this component.
|
||||||
// 'force' option empties existing filters and then applies only 'additionalFilters'
|
// 'force' option empties existing filters and then applies only 'additionalFilters'
|
||||||
@ -217,7 +219,7 @@
|
|||||||
if (filters.stats.length != 0)
|
if (filters.stats.length != 0)
|
||||||
items.push({ metricStats: filters.stats.map((st) => { return { metricName: st.field, range: { from: st.from, to: st.to }} }) });
|
items.push({ metricStats: filters.stats.map((st) => { return { metricName: st.field, range: { from: st.from, to: st.to }} }) });
|
||||||
|
|
||||||
dispatch("update-filters", { filters: items });
|
applyFilters({ filters: items });
|
||||||
changeURL();
|
changeURL();
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
@ -296,7 +298,7 @@
|
|||||||
<!-- Dropdown-Button -->
|
<!-- Dropdown-Button -->
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{#if showFilter}
|
{#if showFilter}
|
||||||
<ButtonDropdown class="cc-dropdown-on-hover mb-1" style="{(matchedJobs >= -1) ? '' : 'margin-right: 0.5rem;'}">
|
<ButtonDropdown class="cc-dropdown-on-hover mb-1" style={(matchedJobs >= -1) ? '' : 'margin-right: 0.5rem;'}>
|
||||||
<DropdownToggle outline caret color="success">
|
<DropdownToggle outline caret color="success">
|
||||||
<Icon name="sliders" />
|
<Icon name="sliders" />
|
||||||
Filters
|
Filters
|
||||||
|
@ -8,22 +8,21 @@
|
|||||||
- `refresh`: When fired, the upstream component refreshes its contents
|
- `refresh`: When fired, the upstream component refreshes its contents
|
||||||
-->
|
-->
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import { Button, Icon, Input, InputGroup } from "@sveltestrap/sveltestrap";
|
import { Button, Icon, Input, InputGroup } from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
let refreshInterval = $state(null);
|
||||||
|
|
||||||
let refreshInterval = null;
|
|
||||||
let refreshIntervalId = null;
|
let refreshIntervalId = null;
|
||||||
|
|
||||||
function refreshIntervalChanged() {
|
function refreshIntervalChanged() {
|
||||||
if (refreshIntervalId != null) clearInterval(refreshIntervalId);
|
if (refreshIntervalId != null) clearInterval(refreshIntervalId);
|
||||||
|
|
||||||
if (refreshInterval == null) return;
|
if (refreshInterval == null) return;
|
||||||
|
|
||||||
refreshIntervalId = setInterval(() => dispatch("refresh"), refreshInterval);
|
refreshIntervalId = setInterval(() => dispatch("refresh"), refreshInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
export let initially = null;
|
let {
|
||||||
|
initially = null,
|
||||||
|
onRefresh
|
||||||
|
} = $props();
|
||||||
|
|
||||||
if (initially != null) {
|
if (initially != null) {
|
||||||
refreshInterval = initially * 1000;
|
refreshInterval = initially * 1000;
|
||||||
@ -36,7 +35,7 @@
|
|||||||
type="select"
|
type="select"
|
||||||
title="Periodic refresh interval"
|
title="Periodic refresh interval"
|
||||||
bind:value={refreshInterval}
|
bind:value={refreshInterval}
|
||||||
on:change={refreshIntervalChanged}
|
onchange={refreshIntervalChanged}
|
||||||
>
|
>
|
||||||
<option value={null}>No Interval</option>
|
<option value={null}>No Interval</option>
|
||||||
<option value={30 * 1000}>30 Seconds</option>
|
<option value={30 * 1000}>30 Seconds</option>
|
||||||
@ -46,7 +45,7 @@
|
|||||||
</Input>
|
</Input>
|
||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
on:click={() => dispatch("refresh")}
|
onclick={() => onRefresh()}
|
||||||
disabled={refreshInterval != null}
|
disabled={refreshInterval != null}
|
||||||
>
|
>
|
||||||
<Icon name="arrow-clockwise" /> Refresh
|
<Icon name="arrow-clockwise" /> Refresh
|
||||||
|
@ -12,16 +12,19 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { InputGroup, Input, Button, Icon } from "@sveltestrap/sveltestrap";
|
import { InputGroup, Input, Button, Icon } from "@sveltestrap/sveltestrap";
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import { scramble, scrambleNames } from "../utils.js";
|
import { scramble, scrambleNames } from "../utils.js";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
// If page with this component has project preset, keep preset until reset
|
||||||
|
let {
|
||||||
|
presetProject = "",
|
||||||
|
authlevel = null,
|
||||||
|
roles = null,
|
||||||
|
setFilter
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let mode = $state(presetProject ? "jobName" : "project");
|
||||||
|
let term = $state("");
|
||||||
|
|
||||||
export let presetProject = ""; // If page with this component has project preset, keep preset until reset
|
|
||||||
export let authlevel = null;
|
|
||||||
export let roles = null;
|
|
||||||
let mode = presetProject ? "jobName" : "project";
|
|
||||||
let term = "";
|
|
||||||
let user = "";
|
let user = "";
|
||||||
let project = presetProject ? presetProject : "";
|
let project = presetProject ? presetProject : "";
|
||||||
let jobName = "";
|
let jobName = "";
|
||||||
@ -52,7 +55,7 @@
|
|||||||
if (timeoutId != null) clearTimeout(timeoutId);
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
|
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
dispatch("set-filter", {
|
setFilter({
|
||||||
user,
|
user,
|
||||||
project,
|
project,
|
||||||
jobName
|
jobName
|
||||||
@ -65,7 +68,7 @@
|
|||||||
if (timeoutId != null) clearTimeout(timeoutId);
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
|
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
dispatch("set-filter", {
|
setFilter({
|
||||||
project,
|
project,
|
||||||
jobName
|
jobName
|
||||||
});
|
});
|
||||||
@ -91,7 +94,7 @@
|
|||||||
class="form-select"
|
class="form-select"
|
||||||
title="Search Mode"
|
title="Search Mode"
|
||||||
bind:value={mode}
|
bind:value={mode}
|
||||||
on:change={modeChanged}
|
onchange={modeChanged}
|
||||||
>
|
>
|
||||||
{#if !presetProject}
|
{#if !presetProject}
|
||||||
<option value={"project"}>Project</option>
|
<option value={"project"}>Project</option>
|
||||||
@ -104,12 +107,12 @@
|
|||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={term}
|
bind:value={term}
|
||||||
on:change={() => termChanged()}
|
onchange={() => termChanged()}
|
||||||
on:keyup={(event) => termChanged(event.key == "Enter" ? 0 : throttle)}
|
onkeyup={(event) => termChanged(event.key == "Enter" ? 0 : throttle)}
|
||||||
placeholder={presetProject ? `Find ${mode} in ${scrambleNames ? scramble(presetProject) : presetProject} ...` : `Find ${mode} ...`}
|
placeholder={presetProject ? `Find ${mode} in ${scrambleNames ? scramble(presetProject) : presetProject} ...` : `Find ${mode} ...`}
|
||||||
/>
|
/>
|
||||||
{#if presetProject}
|
{#if presetProject}
|
||||||
<Button title="Reset Project" on:click={() => resetProject()}
|
<Button title="Reset Project" onclick={() => resetProject()}
|
||||||
><Icon name="arrow-counterclockwise" /></Button
|
><Icon name="arrow-counterclockwise" /></Button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user