diff --git a/web/frontend/src/Analysis.root.svelte b/web/frontend/src/Analysis.root.svelte
index 0237174..afad314 100644
--- a/web/frontend/src/Analysis.root.svelte
+++ b/web/frontend/src/Analysis.root.svelte
@@ -562,6 +562,7 @@
+
{#snippet histoGridContent(item)}
{:else if $initq?.data && $jobMetrics?.data?.scopedJobStats}
-
+
{#snippet gridContent(item)}
{#if item.data}
{:else}
{/if}
@@ -201,7 +201,8 @@
presetSorting={sorting}
applySorting={(newSort) =>
sorting = {...newSort}
- }/>
+ }
+/>
{:else}
+
{#snippet gridContent(item)}
{item.name}
diff --git a/web/frontend/src/Status.root.svelte b/web/frontend/src/Status.root.svelte
index d7ac25b..7e7fc0c 100644
--- a/web/frontend/src/Status.root.svelte
+++ b/web/frontend/src/Status.root.svelte
@@ -676,6 +676,7 @@
{#if selectedHistograms}
+
{#snippet gridContent(item)}
{:else}
+
{#snippet gridContent(item)}
@@ -390,10 +391,10 @@
presetMetrics={metrics}
cluster={selectedCluster}
configName="plot_list_selectedMetrics"
+ footprintSelect
applyMetrics={(newMetrics) =>
metrics = [...newMetrics]
}
- footprintSelect
/>
{
- return JSON.stringify(a) === JSON.stringify(b);
- }
-
- export let sorting = { field: "startTime", type: "col", order: "DESC" };
- export let matchedListJobs = 0;
- export let metrics = ccconfig.plot_list_selectedMetrics;
- export let showFootprint;
- export let filterBuffer = [];
- export let selectedJobs = [];
-
- let usePaging = ccconfig.job_list_usePaging
- let itemsPerPage = usePaging ? ccconfig.plot_list_jobsPerPage : 10;
- let page = 1;
- let paging = { itemsPerPage, page };
- let filter = [...filterBuffer];
- let lastFilter = [];
- let lastSorting = null;
- let triggerMetricRefresh = false;
-
- function getUnit(m) {
- const rawUnit = globalMetrics.find((gm) => gm.name === m)?.unit
- return (rawUnit?.prefix ? rawUnit.prefix : "") + (rawUnit?.base ? rawUnit.base : "")
- }
+ /* Svelte 5 Props */
+ let {
+ matchedListJobs = $bindable(0),
+ selectedJobs = $bindable([]),
+ metrics = getContext("cc-config").plot_list_selectedMetrics,
+ sorting = { field: "startTime", type: "col", order: "DESC" },
+ showFootprint = false,
+ filterBuffer = [],
+ } = $props();
+ /* Const Init */
+ const ccconfig = getContext("cc-config");
+ const initialized = getContext("initialized");
+ const globalMetrics = getContext("globalMetrics");
+ const usePaging = ccconfig?.job_list_usePaging || false;
+ const jobInfoColumnWidth = 250;
const client = getContextClient();
const query = gql`
query (
@@ -107,50 +94,102 @@
}
`;
- $: jobsStore = queryStore({
- client: client,
- query: query,
- variables: { paging, sorting, filter },
+ /* Var Init */
+ let lastFilter = [];
+ let lastSorting = null;
+
+ /* State Init */
+ let headerPaddingTop = $state(0);
+ let jobs = $state([]);
+ let filter = $state([...filterBuffer]);
+ let page = $state(1);
+ let itemsPerPage = $state(usePaging ? (ccconfig?.plot_list_jobsPerPag || 10) : 10);
+ let triggerMetricRefresh = $state(false);
+ let tableWidth = $state(0);
+
+ /* Derived */
+ let paging = $derived({ itemsPerPage, page });
+ const plotWidth = $derived.by(() => {
+ return Math.floor(
+ (tableWidth - jobInfoColumnWidth) / (metrics.length + (showFootprint ? 1 : 0)) - 10,
+ );
+ });
+ let jobsStore = $derived(queryStore({
+ client: client,
+ query: query,
+ variables: { paging, sorting, filter },
+ })
+ );
+
+ /* Effects */
+ $effect(() => {
+ if ($jobsStore?.data) {
+ matchedListJobs = $jobsStore.data.jobs.count;
+ } else {
+ matchedListJobs = -1
+ }
});
- let jobs = [];
- $: if ($initialized && $jobsStore.data) {
- if (usePaging) {
- jobs = [...$jobsStore.data.jobs.items]
- } else { // Prevents jump to table head in continiuous mode, only if no change in sort or filter
- if (equalsCheck(filter, lastFilter) && equalsCheck(sorting, lastSorting)) {
- // console.log('Both Equal: Continuous Addition ... Set None')
- jobs = jobs.concat([...$jobsStore.data.jobs.items])
- } else if (equalsCheck(filter, lastFilter)) {
- // console.log('Filter Equal: Continuous Reset ... Set lastSorting')
- lastSorting = { ...sorting }
- jobs = [...$jobsStore.data.jobs.items]
- } else if (equalsCheck(sorting, lastSorting)) {
- // console.log('Sorting Equal: Continuous Reset ... Set lastFilter')
- lastFilter = [ ...filter ]
- jobs = [...$jobsStore.data.jobs.items]
- } else {
- // console.log('None Equal: Continuous Reset ... Set lastBoth')
- lastSorting = { ...sorting }
- lastFilter = [ ...filter ]
- jobs = [...$jobsStore.data.jobs.items]
- }
+ $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 && $jobsStore?.data?.jobs?.hasNextPage) {
+ page += 1
+ };
+ });
+ };
+ });
+
+ $effect(() => {
+ // Triggers (Except Paging)
+ sorting
+ filter
+ // Continous Scroll: Reset jobs and paging if parameters change: Existing entries will not match new selections
+ if (!usePaging) {
+ jobs = [];
+ page = 1;
}
- }
+ });
- $: if (!usePaging && (sorting || filter)) {
- // Continous Scroll: Reset list and paging if parameters change: Existing entries will not match new selections
- jobs = [];
- paging = { itemsPerPage: 10, page: 1 };
- }
-
- $: matchedListJobs = $jobsStore.data != null ? $jobsStore.data.jobs.count : -1;
+ $effect(() => {
+ if ($initialized && $jobsStore?.data) {
+ if (usePaging) {
+ jobs = [...$jobsStore.data.jobs.items]
+ } else { // Prevents jump to table head in continiuous mode, only if no change in sort or filter
+ if (equalsCheck(filter, lastFilter) && equalsCheck(sorting, lastSorting)) {
+ // console.log('Both Equal: Continuous Addition ... Set None')
+ jobs = jobs.concat([...$jobsStore.data.jobs.items])
+ } else if (equalsCheck(filter, lastFilter)) {
+ // console.log('Filter Equal: Continuous Reset ... Set lastSorting')
+ lastSorting = { ...sorting }
+ jobs = [...$jobsStore.data.jobs.items]
+ } else if (equalsCheck(sorting, lastSorting)) {
+ // console.log('Sorting Equal: Continuous Reset ... Set lastFilter')
+ lastFilter = [ ...filter ]
+ jobs = [...$jobsStore.data.jobs.items]
+ } else {
+ // console.log('None Equal: Continuous Reset ... Set lastBoth')
+ lastSorting = { ...sorting }
+ lastFilter = [ ...filter ]
+ jobs = [...$jobsStore.data.jobs.items]
+ }
+ }
+ };
+ });
+ /* Functions */
// Force refresh list with existing unchanged variables (== usually would not trigger reactivity)
export function refreshJobs() {
if (!usePaging) {
jobs = []; // Empty Joblist before refresh, prevents infinite buildup
- paging = { itemsPerPage: 10, page: 1 };
+ page = 1;
}
jobsStore = queryStore({
client: client,
@@ -178,8 +217,26 @@
filter = filters;
}
page = 1;
- paging = paging = { page, itemsPerPage };
- }
+ };
+
+ function updateConfiguration(value, newPage) {
+ updateConfigurationMutation({
+ name: "plot_list_jobsPerPage",
+ value: value,
+ }).subscribe((res) => {
+ if (res.fetching === false && !res.error) {
+ jobs = [] // Empty List
+ paging = { itemsPerPage: value, page: newPage }; // Trigger reload of jobList
+ } else if (res.fetching === false && res.error) {
+ throw res.error;
+ }
+ });
+ };
+
+ function getUnit(m) {
+ const rawUnit = globalMetrics.find((gm) => gm.name === m)?.unit
+ return (rawUnit?.prefix ? rawUnit.prefix : "") + (rawUnit?.base ? rawUnit.base : "")
+ };
const updateConfigurationMutation = ({ name, value }) => {
return mutationStore({
@@ -193,52 +250,11 @@
});
};
- function updateConfiguration(value, page) {
- updateConfigurationMutation({
- name: "plot_list_jobsPerPage",
- value: value,
- }).subscribe((res) => {
- if (res.fetching === false && !res.error) {
- jobs = [] // Empty List
- paging = { itemsPerPage: value, page: page }; // Trigger reload of jobList
- } else if (res.fetching === false && res.error) {
- throw res.error;
- }
- });
+ const equalsCheck = (a, b) => {
+ return JSON.stringify(a) === JSON.stringify(b);
}
- if (!usePaging) {
- window.addEventListener('scroll', () => {
- let {
- scrollTop,
- scrollHeight,
- clientHeight
- } = document.documentElement;
-
- // Add 100 px offset to trigger load earlier
- if (scrollTop + clientHeight >= scrollHeight - 100 && $jobsStore.data != null && $jobsStore.data.jobs.hasNextPage) {
- let pendingPaging = { ...paging }
- pendingPaging.page += 1
- paging = pendingPaging
- };
- });
- };
-
- let plotWidth = null;
- let tableWidth = null;
- let jobInfoColumnWidth = 250;
-
- $: if (showFootprint) {
- plotWidth = Math.floor(
- (tableWidth - jobInfoColumnWidth) / (metrics.length + 1) - 10,
- );
- } else {
- plotWidth = Math.floor(
- (tableWidth - jobInfoColumnWidth) / metrics.length - 10,
- );
- }
-
- let headerPaddingTop = 0;
+ /* Init Header */
stickyHeader(
".cc-table-wrapper > table.table >thead > tr > th.position-sticky:nth-child(1)",
(x) => (headerPaddingTop = x),
@@ -292,8 +308,8 @@
{:else}
{#each jobs as job (job.id)}
selectedJobs = [...selectedJobs, detail]}
- on:unselect-job={({detail}) => selectedJobs = selectedJobs.filter(item => item !== detail)}
+ selectJob={(detail) => selectedJobs = [...selectedJobs, detail]}
+ unselectJob={(detail) => selectedJobs = selectedJobs.filter(item => item !== detail)}
/>
{:else}
diff --git a/web/frontend/src/generic/joblist/JobInfo.svelte b/web/frontend/src/generic/joblist/JobInfo.svelte
index 5886c61..bb8b90b 100644
--- a/web/frontend/src/generic/joblist/JobInfo.svelte
+++ b/web/frontend/src/generic/joblist/JobInfo.svelte
@@ -20,7 +20,7 @@
username = null,
authlevel= null,
roles = null,
- isSelected = null,
+ isSelected = $bindable(),
showSelect = false,
} = $props();
@@ -89,10 +89,8 @@
}}>
{#if isSelected}
- {:else if isSelected == false}
-
- {:else}
-
+ {:else }
+
{/if}
import { queryStore, gql, getContextClient } from "@urql/svelte";
- import { getContext, createEventDispatcher } from "svelte";
+ import { getContext } from "svelte";
import { Card, Spinner } from "@sveltestrap/sveltestrap";
import { maxScope, checkMetricDisabled } from "../utils.js";
import JobInfo from "./JobInfo.svelte";
import MetricPlot from "../plots/MetricPlot.svelte";
import JobFootprint from "../helper/JobFootprint.svelte";
- export let job;
- export let metrics;
- export let plotWidth;
- export let plotHeight = 275;
- export let showFootprint;
- export let triggerMetricRefresh = false;
- export let previousSelect = false;
+ /* Svelte 5 Props */
+ let {
+ triggerMetricRefresh = $bindable(false),
+ job,
+ metrics,
+ plotWidth,
+ plotHeight = 275,
+ showFootprint,
+ previousSelect = false,
+ selectJob,
+ unselectJob
+ } = $props();
- const dispatch = createEventDispatcher();
- const resampleConfig = getContext("resampling") || null;
- const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
-
- let { id } = job;
- let scopes = job.numNodes == 1
- ? job.numAcc >= 1
+ /* Const Init */
+ const client = getContextClient();
+ const jobId = job.id;
+ const cluster = getContext("clusters").find((c) => c.name == job.cluster);
+ const scopes = (job.numNodes == 1)
+ ? (job.numAcc >= 1)
? ["core", "accelerator"]
: ["core"]
: ["node"];
- let selectedResolution = resampleDefault;
- let zoomStates = {};
- let thresholdStates = {};
-
- $: isSelected = previousSelect || null;
-
- const cluster = getContext("clusters").find((c) => c.name == job.cluster);
- const client = getContextClient();
+ const resampleConfig = getContext("resampling") || null;
+ const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
const query = gql`
query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!, $selectedResolution: Int) {
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes, resolution: $selectedResolution) {
@@ -77,52 +75,59 @@
}
`;
- function handleZoom(detail, metric) {
- if ( // States have to differ, causes deathloop if just set
- (zoomStates[metric]?.x?.min !== detail?.lastZoomState?.x?.min) &&
- (zoomStates[metric]?.y?.max !== detail?.lastZoomState?.y?.max)
- ) {
- zoomStates[metric] = {...detail.lastZoomState}
+ /* State Init */
+ let selectedResolution = $state(resampleDefault);
+ let zoomStates = $state({});
+ let thresholdStates = $state({});
+
+ /* Derived */
+ let isSelected = $derived(previousSelect);
+ let metricsQuery = $derived(queryStore({
+ client: client,
+ query: query,
+ variables: { id: jobId, metrics, scopes, selectedResolution },
+ })
+ );
+
+ /* Effects */
+ $effect(() => {
+ if (job.state === 'running' && triggerMetricRefresh === true) {
+ refreshMetrics();
}
+ });
- if ( // States have to differ, causes deathloop if just set
- detail?.lastThreshold &&
- thresholdStates[metric] !== detail.lastThreshold
- ) { // Handle to correctly reset on summed metric scope change
- thresholdStates[metric] = detail.lastThreshold;
- }
+ $effect(() => {
+ if (isSelected == true && previousSelect == false) {
+ selectJob(jobId)
+ } else if (isSelected == false && previousSelect == true) {
+ unselectJob(jobId)
+ }
+ });
- if (detail?.newRes) { // Triggers GQL
- selectedResolution = detail.newRes
+ /* Functions */
+ function handleZoom(detail, metric) {
+ // Buffer last zoom state to allow seamless zoom on rerender
+ // console.log('Update zoomState for/with:', metric, {...detail.lastZoomState})
+ zoomStates[metric] = detail?.lastZoomState ? {...detail.lastZoomState} : null;
+ // Handle to correctly reset on summed metric scope change
+ // console.log('Update thresholdState for/with:', metric, detail.lastThreshold)
+ thresholdStates[metric] = detail?.lastThreshold ? detail.lastThreshold : null;
+ // Triggers GQL
+ if (detail?.newRes) {
+ // console.log('Update selectedResolution for/with:', metric, detail.newRes)
+ selectedResolution = detail.newRes;
}
}
- $: metricsQuery = queryStore({
- client: client,
- query: query,
- variables: { id, metrics, scopes, selectedResolution },
- });
-
function refreshMetrics() {
metricsQuery = queryStore({
client: client,
query: query,
- variables: { id, metrics, scopes, selectedResolution },
+ variables: { id: jobId, metrics, scopes, selectedResolution },
// requestPolicy: 'network-only' // use default cache-first for refresh
});
}
- $: if (job.state === 'running' && triggerMetricRefresh === true) {
- refreshMetrics();
- }
-
- $: if (isSelected == true && previousSelect == false) {
- dispatch("select-job", job.id)
- } else if (isSelected == false && previousSelect == true) {
- dispatch("unselect-job", job.id)
- }
-
- // Helper
const selectScope = (jobMetrics) =>
jobMetrics.reduce(
(a, b) =>
@@ -157,7 +162,6 @@
return jobMetric;
}
});
-
@@ -196,7 +200,7 @@
{#if metric.disabled == false && metric.data}
{ handleZoom(detail, metric.data.name) }}
+ on:zoom={({detail}) => handleZoom(detail, metric.data.name)}
height={plotHeight}
timestep={metric.data.metric.timestep}
scope={metric.data.scope}
diff --git a/web/frontend/src/job/Metric.svelte b/web/frontend/src/job/Metric.svelte
index 7040e9e..eba4102 100644
--- a/web/frontend/src/job/Metric.svelte
+++ b/web/frontend/src/job/Metric.svelte
@@ -81,7 +81,7 @@
/* State Init */
let requestedScopes = $state(presetScopes);
- let selectedResolution = $state(resampleConfig ? resampleDefault : 0);
+ let selectedResolution = $state(resampleDefault);
let selectedHost = $state(null);
let zoomState = $state(null);