diff --git a/web/frontend/src/Header.svelte b/web/frontend/src/Header.svelte index deec76a..2473542 100644 --- a/web/frontend/src/Header.svelte +++ b/web/frontend/src/Header.svelte @@ -24,25 +24,21 @@ import NavbarLinks from "./header/NavbarLinks.svelte"; import NavbarTools from "./header/NavbarTools.svelte"; + /* Svelte 5 Props */ let { username, authlevel, clusters, subClusters, roles } = $props(); - let isOpen = $state(false); - let screenSize = $state(0); - - let showMax = $derived(screenSize >= 1500); - let showMid = $derived(screenSize < 1500 && screenSize >= 1300); - let showSml = $derived(screenSize < 1300 && screenSize >= 768); - let showBrg = $derived(screenSize < 768); - + /* Const Init */ const jobsTitle = new Map(); jobsTitle.set(2, "Job Search"); jobsTitle.set(3, "Managed Jobs"); jobsTitle.set(4, "Jobs"); jobsTitle.set(5, "Jobs"); + const usersTitle = new Map(); usersTitle.set(3, "Managed Users"); usersTitle.set(4, "Users"); usersTitle.set(5, "Users"); + const projectsTitle = new Map(); projectsTitle.set(3, "Managed Projects"); projectsTitle.set(4, "Projects"); @@ -122,6 +118,16 @@ menu: "Info", }, ]; + + /* State Init */ + let isOpen = $state(false); + let screenSize = $state(0); + + /* Derived Vars */ + let showMax = $derived(screenSize >= 1500); + let showMid = $derived(screenSize < 1500 && screenSize >= 1300); + let showSml = $derived(screenSize < 1300 && screenSize >= 768); + let showBrg = $derived(screenSize < 768); diff --git a/web/frontend/src/Jobs.root.svelte b/web/frontend/src/Jobs.root.svelte index 1c13777..5f5f3bd 100644 --- a/web/frontend/src/Jobs.root.svelte +++ b/web/frontend/src/Jobs.root.svelte @@ -27,13 +27,15 @@ import Sorting from "./generic/select/SortSelection.svelte"; import MetricSelection from "./generic/select/MetricSelection.svelte"; - const { query: initq } = init(); - const ccconfig = getContext("cc-config"); - - // Svelte 5 Props + /* Svelte 5 Props */ let { filterPresets, authlevel, roles } = $props(); - // Svelte 5 Reactive Vars + /* Const Init */ + const { query: initq } = init(); + const ccconfig = getContext("cc-config"); + const presetProject = filterPresets?.project ? filterPresets.project : "" + + /* State Init */ 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 = $state([]); let filterBuffer = $state([]); @@ -56,20 +58,14 @@ : !!ccconfig.plot_list_showFootprint ); - // Classic Inits - let presetProject = filterPresets?.project ? filterPresets.project : "" - - // The filterPresets are handled by the Filters component, - // 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. - onMount(() => filterComponent.updateFilters()); - + /* Functions */ function resetJobSelection() { if (filterComponent && selectedJobs.length === 0) { filterComponent.updateFilters({ dbId: [] }); }; }; + /* Reactive Effects */ $effect(() => { // Reactive : Trigger Effect selectedJobs.length @@ -79,6 +75,11 @@ }); }); + /* On Mount */ + // The filterPresets are handled by the Filters component, + // 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. + onMount(() => filterComponent.updateFilters()); diff --git a/web/frontend/src/generic/Filters.svelte b/web/frontend/src/generic/Filters.svelte index d56bb84..ca25904 100644 --- a/web/frontend/src/generic/Filters.svelte +++ b/web/frontend/src/generic/Filters.svelte @@ -25,18 +25,18 @@ ButtonDropdown, Icon, } from "@sveltestrap/sveltestrap"; - import Tag from "./helper/Tag.svelte"; import Info from "./filters/InfoBox.svelte"; import Cluster from "./filters/Cluster.svelte"; import JobStates, { allJobStates } from "./filters/JobStates.svelte"; - import StartTime from "./filters/StartTime.svelte"; - import Tags from "./filters/Tags.svelte"; + import StartTime, { startTimeSelectOptions } from "./filters/StartTime.svelte"; import Duration from "./filters/Duration.svelte"; - import Energy from "./filters/Energy.svelte"; + import Tags from "./filters/Tags.svelte"; + import Tag from "./helper/Tag.svelte"; import Resources from "./filters/Resources.svelte"; + import Energy from "./filters/Energy.svelte"; import Statistics from "./filters/Stats.svelte"; - // Svelte 5 Props + /* Svelte 5 Props */ let { menuText = null, filterPresets = {}, @@ -47,59 +47,55 @@ applyFilters } = $props(); - const startTimeSelectOptions = [ - { range: "", rangeLabel: "No Selection"}, - { range: "last6h", rangeLabel: "Last 6hrs"}, - { range: "last24h", rangeLabel: "Last 24hrs"}, - { range: "last7d", rangeLabel: "Last 7 days"}, - { range: "last30d", rangeLabel: "Last 30 days"} - ]; - + /* Const Init */ const nodeMatchLabels = { eq: "", contains: " Contains", } - const filterReset = { - projectMatch: "contains", - userMatch: "contains", + // Direct Filters + dbId: [], + jobId: "", jobIdMatch: "eq", - nodeMatch: "eq", - + arrayJobId: null, + jobName: "", + // View Filters + project: "", + projectMatch: "contains", + user: "", + userMatch: "contains", + // Filter Modals cluster: null, partition: null, states: allJobStates, startTime: { from: null, to: null, range: ""}, - tags: [], duration: { lessThan: null, moreThan: null, from: null, to: null, }, - dbId: [], - jobId: "", - arrayJobId: null, - user: "", - project: "", - jobName: "", - - node: null, - energy: { from: null, to: null }, + tags: [], numNodes: { from: null, to: null }, numHWThreads: { from: null, to: null }, numAccelerators: { from: null, to: null }, - + node: null, + nodeMatch: "eq", + energy: { from: null, to: null }, stats: [], }; - // Svelte 5 Reactive Vars + /* State Init */ let filters = $state({ - projectMatch: filterPresets.projectMatch || "contains", - userMatch: filterPresets.userMatch || "contains", + dbId: filterPresets.dbId || [], + jobId: filterPresets.jobId || "", jobIdMatch: filterPresets.jobIdMatch || "eq", - nodeMatch: filterPresets.nodeMatch || "eq", - + arrayJobId: filterPresets.arrayJobId || null, + jobName: filterPresets.jobName || "", + project: filterPresets.project || "", + projectMatch: filterPresets.projectMatch || "contains", + user: filterPresets.user || "", + userMatch: filterPresets.userMatch || "contains", cluster: filterPresets.cluster || null, partition: filterPresets.partition || null, states: @@ -107,41 +103,33 @@ ? [filterPresets.state].flat() : allJobStates, startTime: filterPresets.startTime || { from: null, to: null, range: ""}, - tags: filterPresets.tags || [], duration: filterPresets.duration || { lessThan: null, moreThan: null, from: null, to: null, }, - dbId: filterPresets.dbId || [], - jobId: filterPresets.jobId || "", - arrayJobId: filterPresets.arrayJobId || null, - user: filterPresets.user || "", - project: filterPresets.project || "", - jobName: filterPresets.jobName || "", - - node: filterPresets.node || null, - energy: filterPresets.energy || { from: null, to: null }, + tags: filterPresets.tags || [], numNodes: filterPresets.numNodes || { from: null, to: null }, numHWThreads: filterPresets.numHWThreads || { from: null, to: null }, numAccelerators: filterPresets.numAccelerators || { from: null, to: null }, - + node: filterPresets.node || null, + nodeMatch: filterPresets.nodeMatch || "eq", + energy: filterPresets.energy || { from: null, to: null }, stats: filterPresets.stats || [], }); + /* Opened States */ let isClusterOpen = $state(false) let isJobStatesOpen = $state(false) let isStartTimeOpen = $state(false) - let isTagsOpen = $state(false) let isDurationOpen = $state(false) - let isEnergyOpen = $state(false) + let isTagsOpen = $state(false) let isResourcesOpen = $state(false) + let isEnergyOpen = $state(false) let isStatsOpen = $state(false) - let isNodesModified = $state(false) - let isHwthreadsModified = $state(false) - let isAccsModified = $state(false) + /* Functions */ // Can be called from the outside to trigger a 'update' event from this component. // 'force' option empties existing filters and then applies only 'additionalFilters' export function updateFilters(additionalFilters = null, force = false) { @@ -155,10 +143,20 @@ } // Construct New Filter let items = []; + if (filters.dbId.length != 0) + items.push({ dbId: filters.dbId }); + if (filters.jobId) + items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } }); + if (filters.arrayJobId != null) + items.push({ arrayJobId: filters.arrayJobId }); + if (filters.jobName) items.push({ jobName: { contains: filters.jobName } }); + if (filters.project) + items.push({ project: { [filters.projectMatch]: filters.project } }); + if (filters.user) + items.push({ user: { [filters.userMatch]: filters.user } }); if (filters.cluster) items.push({ cluster: { eq: filters.cluster } }); - if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } }); if (filters.partition) items.push({ partition: { eq: filters.partition } }); - if (filters.states.length != allJobStates.length) + if (filters.states.length != allJobStates?.length) items.push({ state: filters.states }); if (filters.startTime.from || filters.startTime.to) items.push({ @@ -168,7 +166,6 @@ items.push({ startTime: { range: filters.startTime.range }, }); - if (filters.tags.length != 0) items.push({ tags: filters.tags }); if (filters.duration.from || filters.duration.to) items.push({ duration: { from: filters.duration.from, to: filters.duration.to }, @@ -177,21 +174,11 @@ items.push({ duration: { from: 0, to: filters.duration.lessThan } }); if (filters.duration.moreThan) items.push({ duration: { from: filters.duration.moreThan, to: 604800 } }); // 7 days to include special jobs with long runtimes - if (filters.energy.from || filters.energy.to) - items.push({ - energy: { from: filters.energy.from, to: filters.energy.to }, - }); - if (filters.dbId.length != 0) - items.push({ dbId: filters.dbId }); - if (filters.jobId) - items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } }); - if (filters.arrayJobId != null) - items.push({ arrayJobId: filters.arrayJobId }); + if (filters.tags.length != 0) items.push({ tags: filters.tags }); if (filters.numNodes.from != null || filters.numNodes.to != null) { items.push({ numNodes: { from: filters.numNodes.from, to: filters.numNodes.to }, }); - isNodesModified = true; } if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) { items.push({ @@ -200,7 +187,6 @@ to: filters.numHWThreads.to, }, }); - isHwthreadsModified = true; } if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) { items.push({ @@ -209,13 +195,12 @@ to: filters.numAccelerators.to, }, }); - isAccsModified = true; } - if (filters.user) - items.push({ user: { [filters.userMatch]: filters.user } }); - if (filters.project) - items.push({ project: { [filters.projectMatch]: filters.project } }); - if (filters.jobName) items.push({ jobName: { contains: filters.jobName } }); + if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } }); + if (filters.energy.from || filters.energy.to) + items.push({ + energy: { from: filters.energy.from, to: filters.energy.to }, + }); if (filters.stats.length != 0) items.push({ metricStats: filters.stats.map((st) => { return { metricName: st.field, range: { from: st.from, to: st.to }} }) }); @@ -228,20 +213,7 @@ const dateToUnixEpoch = (rfc3339) => Math.floor(Date.parse(rfc3339) / 1000); let opts = []; - if (filters.cluster) opts.push(`cluster=${filters.cluster}`); - 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.partition) opts.push(`partition=${filters.partition}`); - if (filters.states.length != allJobStates.length) - for (let state of filters.states) opts.push(`state=${state}`); - if (filters.startTime.from && filters.startTime.to) - opts.push( - `startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`, - ); - if (filters.startTime.range) { - opts.push(`startTime=${filters.startTime.range}`) - } + // Direct Filters if (filters.dbId.length != 0) { for (let dbi of filters.dbId) { opts.push(`dbId=${dbi}`); @@ -256,21 +228,12 @@ } if (filters.jobIdMatch != "eq") opts.push(`jobIdMatch=${filters.jobIdMatch}`); // "eq" is default-case - for (let tag of filters.tags) opts.push(`tag=${tag}`); - if (filters.duration.from && filters.duration.to) - opts.push(`duration=${filters.duration.from}-${filters.duration.to}`); - if (filters.duration.lessThan) - opts.push(`duration=0-${filters.duration.lessThan}`); - if (filters.duration.moreThan) - opts.push(`duration=${filters.duration.moreThan}-604800`); - if (filters.energy.from && filters.energy.to) - opts.push(`energy=${filters.energy.from}-${filters.energy.to}`); - if (filters.numNodes.from && filters.numNodes.to) - opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`); - if (filters.numHWThreads.from && filters.numHWThreads.to) - opts.push(`numHWThreads=${filters.numHWThreads.from}-${filters.numHWThreads.to}`); - if (filters.numAccelerators.from && filters.numAccelerators.to) - opts.push(`numAccelerators=${filters.numAccelerators.from}-${filters.numAccelerators.to}`); + if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`); + if (filters.jobName) opts.push(`jobName=${filters.jobName}`); + // View Filters + if (filters.project) opts.push(`project=${filters.project}`); + if (filters.project && filters.projectMatch != "contains") // "contains" is default-case + opts.push(`projectMatch=${filters.projectMatch}`); if (filters.user.length != 0) if (filters.userMatch != "in") { opts.push(`user=${filters.user}`); @@ -279,16 +242,42 @@ } if (filters.userMatch != "contains") // "contains" is default-case opts.push(`userMatch=${filters.userMatch}`); - if (filters.project) opts.push(`project=${filters.project}`); - if (filters.project && filters.projectMatch != "contains") // "contains" is default-case - opts.push(`projectMatch=${filters.projectMatch}`); - if (filters.jobName) opts.push(`jobName=${filters.jobName}`); - if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`); + // Filter Modals + if (filters.cluster) opts.push(`cluster=${filters.cluster}`); + if (filters.partition) opts.push(`partition=${filters.partition}`); + if (filters.states.length != allJobStates?.length) + for (let state of filters.states) opts.push(`state=${state}`); + if (filters.startTime.from && filters.startTime.to) + opts.push( + `startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`, + ); + if (filters.startTime.range) { + opts.push(`startTime=${filters.startTime.range}`) + } + if (filters.duration.from && filters.duration.to) + opts.push(`duration=${filters.duration.from}-${filters.duration.to}`); + if (filters.duration.lessThan) + opts.push(`duration=0-${filters.duration.lessThan}`); + if (filters.duration.moreThan) + opts.push(`duration=${filters.duration.moreThan}-604800`); + if (filters.tags.length != 0) + for (let tag of filters.tags) opts.push(`tag=${tag}`); + if (filters.numNodes.from && filters.numNodes.to) + opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`); + if (filters.numHWThreads.from && filters.numHWThreads.to) + opts.push(`numHWThreads=${filters.numHWThreads.from}-${filters.numHWThreads.to}`); + if (filters.numAccelerators.from && filters.numAccelerators.to) + opts.push(`numAccelerators=${filters.numAccelerators.from}-${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 && filters.energy.to) + opts.push(`energy=${filters.energy.from}-${filters.energy.to}`); if (filters.stats.length != 0) for (let stat of filters.stats) { opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`); } - + // Build && Return if (opts.length == 0 && window.location.search.length <= 1) return; let newurl = `${window.location.pathname}?${opts.join("&")}`; window.history.replaceState(null, "", newurl); @@ -309,36 +298,36 @@ {menuText} {/if} - (isClusterOpen = true)}> + (isClusterOpen = true)}> Cluster/Partition - (isJobStatesOpen = true)}> + (isJobStatesOpen = true)}> Job States - (isStartTimeOpen = true)}> + (isStartTimeOpen = true)}> Start Time - (isDurationOpen = true)}> + (isDurationOpen = true)}> Duration - (isTagsOpen = true)}> + (isTagsOpen = true)}> Tags - (isResourcesOpen = true)}> + (isResourcesOpen = true)}> Resources - (isEnergyOpen = true)}> + (isEnergyOpen = true)}> Energy - (isStatsOpen = true)}> - (isStatsOpen = true)} /> Statistics + (isStatsOpen = true)}> + (isStatsOpen = true)} /> Statistics {#if startTimeQuickSelect} Start Time Quick Selection {#each startTimeSelectOptions.filter((stso) => stso.range !== "") as { rangeLabel, range }} { + onclick={() => { filters.startTime.from = null filters.startTime.to = null filters.startTime.range = range; @@ -364,7 +353,7 @@ {#if showFilter} {#if filters.cluster} - (isClusterOpen = true)}> + (isClusterOpen = true)}> {filters.cluster} {#if filters.partition} ({filters.partition}) @@ -372,14 +361,14 @@ {/if} - {#if filters.states.length != allJobStates.length} - (isJobStatesOpen = true)}> + {#if filters.states.length != allJobStates?.length} + (isJobStatesOpen = true)}> {filters.states.join(", ")} {/if} {#if filters.startTime.from || filters.startTime.to} - (isStartTimeOpen = true)}> + (isStartTimeOpen = true)}> {new Date(filters.startTime.from).toLocaleString()} - {new Date( filters.startTime.to, ).toLocaleString()} @@ -387,13 +376,13 @@ {/if} {#if filters.startTime.range} - (isStartTimeOpen = true)}> + (isStartTimeOpen = true)}> {startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel } {/if} {#if filters.duration.from || filters.duration.to} - (isDurationOpen = true)}> + (isDurationOpen = true)}> {Math.floor(filters.duration.from / 3600)}h:{Math.floor( (filters.duration.from % 3600) / 60, )}m - @@ -404,7 +393,7 @@ {/if} {#if filters.duration.lessThan} - (isDurationOpen = true)}> + (isDurationOpen = true)}> Duration less than {Math.floor( filters.duration.lessThan / 3600, )}h:{Math.floor((filters.duration.lessThan % 3600) / 60)}m @@ -412,7 +401,7 @@ {/if} {#if filters.duration.moreThan} - (isDurationOpen = true)}> + (isDurationOpen = true)}> Duration more than {Math.floor( filters.duration.moreThan / 3600, )}h:{Math.floor((filters.duration.moreThan % 3600) / 60)}m @@ -420,47 +409,45 @@ {/if} {#if filters.tags.length != 0} - (isTagsOpen = true)}> + (isTagsOpen = true)}> {#each filters.tags as tagId} - {#key tagId} - - {/key} + {/each} {/if} - {#if filters.numNodes.from != null || filters.numNodes.to != null || filters.numHWThreads.from != null || filters.numHWThreads.to != null || filters.numAccelerators.from != null || filters.numAccelerators.to != null} - (isResourcesOpen = true)}> - {#if isNodesModified} + {#if filters.numNodes.from != null || filters.numNodes.to != null} + (isResourcesOpen = true)}> Nodes: {filters.numNodes.from} - {filters.numNodes.to} - {/if} - {#if isNodesModified && isHwthreadsModified}, - {/if} - {#if isHwthreadsModified} + + {/if} + + {#if filters.numHWThreads.from != null || filters.numHWThreads.to != null} + (isResourcesOpen = true)}> HWThreads: {filters.numHWThreads.from} - {filters.numHWThreads.to} - {/if} - {#if (isNodesModified || isHwthreadsModified) && isAccsModified}, - {/if} - {#if isAccsModified} + + {/if} + + {#if filters.numAccelerators.from != null || filters.numAccelerators.to != null} + (isResourcesOpen = true)}> Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to} - {/if} {/if} {#if filters.node != null} - (isResourcesOpen = true)}> + (isResourcesOpen = true)}> Node{nodeMatchLabels[filters.nodeMatch]}: {filters.node} {/if} {#if filters.energy.from || filters.energy.to} - (isEnergyOpen = true)}> + (isEnergyOpen = true)}> Total Energy: {filters.energy.from} - {filters.energy.to} {/if} {#if filters.stats.length > 0} - (isStatsOpen = true)}> + (isStatsOpen = true)}> {filters.stats .map((stat) => `${stat.field}: ${stat.from} - ${stat.to}`) .join(", ")} @@ -469,69 +456,62 @@ {/if} updateFilters()} + presetCluster={filters.cluster} + presetPartition={filters.partition} + {disableClusterSelection} + setFilter={(filter) => updateFilters(filter)} /> updateFilters()} + presetStates={filters.states} + setFilter={(filter) => updateFilters(filter)} /> updateFilters()} + presetStartTime={filters.startTime} + setFilter={(filter) => updateFilters(filter)} /> updateFilters()} + presetDuration={filters.duration} + setFilter={(filter) => updateFilters(filter)} /> updateFilters()} + presetTags={filters.tags} + setFilter={(filter) => updateFilters(filter)} /> updateFilters()} -/> - - updateFilters()} + activeCluster={filters.cluster} + presetNumNodes={filters.numNodes} + presetNumHWThreads={filters.numHWThreads} + presetNumAccelerators={filters.numAccelerators} + presetNamedNode={filters.node} + presetNodeMatch={filters.nodeMatch} + setFilter={(filter) => updateFilters(filter)} /> updateFilters()} + presetEnergy={filters.energy} + setFilter={(filter) => updateFilters(filter)} /> + updateFilters(filter)} +/> + + +