mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-06-08 00:23:48 +02:00
Migrate and fix filter component and subcomponents
This commit is contained in:
parent
ffd596e2c7
commit
0b529a5c3c
@ -24,25 +24,21 @@
|
|||||||
import NavbarLinks from "./header/NavbarLinks.svelte";
|
import NavbarLinks from "./header/NavbarLinks.svelte";
|
||||||
import NavbarTools from "./header/NavbarTools.svelte";
|
import NavbarTools from "./header/NavbarTools.svelte";
|
||||||
|
|
||||||
|
/* Svelte 5 Props */
|
||||||
let { username, authlevel, clusters, subClusters, roles } = $props();
|
let { username, authlevel, clusters, subClusters, roles } = $props();
|
||||||
|
|
||||||
let isOpen = $state(false);
|
/* Const Init */
|
||||||
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 jobsTitle = new Map();
|
const jobsTitle = new Map();
|
||||||
jobsTitle.set(2, "Job Search");
|
jobsTitle.set(2, "Job Search");
|
||||||
jobsTitle.set(3, "Managed Jobs");
|
jobsTitle.set(3, "Managed Jobs");
|
||||||
jobsTitle.set(4, "Jobs");
|
jobsTitle.set(4, "Jobs");
|
||||||
jobsTitle.set(5, "Jobs");
|
jobsTitle.set(5, "Jobs");
|
||||||
|
|
||||||
const usersTitle = new Map();
|
const usersTitle = new Map();
|
||||||
usersTitle.set(3, "Managed Users");
|
usersTitle.set(3, "Managed Users");
|
||||||
usersTitle.set(4, "Users");
|
usersTitle.set(4, "Users");
|
||||||
usersTitle.set(5, "Users");
|
usersTitle.set(5, "Users");
|
||||||
|
|
||||||
const projectsTitle = new Map();
|
const projectsTitle = new Map();
|
||||||
projectsTitle.set(3, "Managed Projects");
|
projectsTitle.set(3, "Managed Projects");
|
||||||
projectsTitle.set(4, "Projects");
|
projectsTitle.set(4, "Projects");
|
||||||
@ -122,6 +118,16 @@
|
|||||||
menu: "Info",
|
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);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerWidth={screenSize} />
|
<svelte:window bind:innerWidth={screenSize} />
|
||||||
|
@ -27,13 +27,15 @@
|
|||||||
import Sorting from "./generic/select/SortSelection.svelte";
|
import Sorting from "./generic/select/SortSelection.svelte";
|
||||||
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
||||||
|
|
||||||
const { query: initq } = init();
|
/* Svelte 5 Props */
|
||||||
const ccconfig = getContext("cc-config");
|
|
||||||
|
|
||||||
// Svelte 5 Props
|
|
||||||
let { filterPresets, authlevel, roles } = $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 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 selectedJobs = $state([]);
|
||||||
let filterBuffer = $state([]);
|
let filterBuffer = $state([]);
|
||||||
@ -56,20 +58,14 @@
|
|||||||
: !!ccconfig.plot_list_showFootprint
|
: !!ccconfig.plot_list_showFootprint
|
||||||
);
|
);
|
||||||
|
|
||||||
// Classic Inits
|
/* Functions */
|
||||||
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());
|
|
||||||
|
|
||||||
function resetJobSelection() {
|
function resetJobSelection() {
|
||||||
if (filterComponent && selectedJobs.length === 0) {
|
if (filterComponent && selectedJobs.length === 0) {
|
||||||
filterComponent.updateFilters({ dbId: [] });
|
filterComponent.updateFilters({ dbId: [] });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Reactive Effects */
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Reactive : Trigger Effect
|
// Reactive : Trigger Effect
|
||||||
selectedJobs.length
|
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());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- ROW1: Status-->
|
<!-- ROW1: Status-->
|
||||||
|
@ -25,18 +25,18 @@
|
|||||||
ButtonDropdown,
|
ButtonDropdown,
|
||||||
Icon,
|
Icon,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import Tag from "./helper/Tag.svelte";
|
|
||||||
import Info from "./filters/InfoBox.svelte";
|
import Info from "./filters/InfoBox.svelte";
|
||||||
import Cluster from "./filters/Cluster.svelte";
|
import Cluster from "./filters/Cluster.svelte";
|
||||||
import JobStates, { allJobStates } from "./filters/JobStates.svelte";
|
import JobStates, { allJobStates } from "./filters/JobStates.svelte";
|
||||||
import StartTime from "./filters/StartTime.svelte";
|
import StartTime, { startTimeSelectOptions } from "./filters/StartTime.svelte";
|
||||||
import Tags from "./filters/Tags.svelte";
|
|
||||||
import Duration from "./filters/Duration.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 Resources from "./filters/Resources.svelte";
|
||||||
|
import Energy from "./filters/Energy.svelte";
|
||||||
import Statistics from "./filters/Stats.svelte";
|
import Statistics from "./filters/Stats.svelte";
|
||||||
|
|
||||||
// Svelte 5 Props
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
menuText = null,
|
menuText = null,
|
||||||
filterPresets = {},
|
filterPresets = {},
|
||||||
@ -47,59 +47,55 @@
|
|||||||
applyFilters
|
applyFilters
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const startTimeSelectOptions = [
|
/* Const Init */
|
||||||
{ 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 nodeMatchLabels = {
|
const nodeMatchLabels = {
|
||||||
eq: "",
|
eq: "",
|
||||||
contains: " Contains",
|
contains: " Contains",
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterReset = {
|
const filterReset = {
|
||||||
projectMatch: "contains",
|
// Direct Filters
|
||||||
userMatch: "contains",
|
dbId: [],
|
||||||
|
jobId: "",
|
||||||
jobIdMatch: "eq",
|
jobIdMatch: "eq",
|
||||||
nodeMatch: "eq",
|
arrayJobId: null,
|
||||||
|
jobName: "",
|
||||||
|
// View Filters
|
||||||
|
project: "",
|
||||||
|
projectMatch: "contains",
|
||||||
|
user: "",
|
||||||
|
userMatch: "contains",
|
||||||
|
// Filter Modals
|
||||||
cluster: null,
|
cluster: null,
|
||||||
partition: null,
|
partition: null,
|
||||||
states: allJobStates,
|
states: allJobStates,
|
||||||
startTime: { from: null, to: null, range: ""},
|
startTime: { from: null, to: null, range: ""},
|
||||||
tags: [],
|
|
||||||
duration: {
|
duration: {
|
||||||
lessThan: null,
|
lessThan: null,
|
||||||
moreThan: null,
|
moreThan: null,
|
||||||
from: null,
|
from: null,
|
||||||
to: null,
|
to: null,
|
||||||
},
|
},
|
||||||
dbId: [],
|
tags: [],
|
||||||
jobId: "",
|
|
||||||
arrayJobId: null,
|
|
||||||
user: "",
|
|
||||||
project: "",
|
|
||||||
jobName: "",
|
|
||||||
|
|
||||||
node: null,
|
|
||||||
energy: { from: null, to: null },
|
|
||||||
numNodes: { from: null, to: null },
|
numNodes: { from: null, to: null },
|
||||||
numHWThreads: { from: null, to: null },
|
numHWThreads: { from: null, to: null },
|
||||||
numAccelerators: { from: null, to: null },
|
numAccelerators: { from: null, to: null },
|
||||||
|
node: null,
|
||||||
|
nodeMatch: "eq",
|
||||||
|
energy: { from: null, to: null },
|
||||||
stats: [],
|
stats: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Svelte 5 Reactive Vars
|
/* State Init */
|
||||||
let filters = $state({
|
let filters = $state({
|
||||||
projectMatch: filterPresets.projectMatch || "contains",
|
dbId: filterPresets.dbId || [],
|
||||||
userMatch: filterPresets.userMatch || "contains",
|
jobId: filterPresets.jobId || "",
|
||||||
jobIdMatch: filterPresets.jobIdMatch || "eq",
|
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,
|
cluster: filterPresets.cluster || null,
|
||||||
partition: filterPresets.partition || null,
|
partition: filterPresets.partition || null,
|
||||||
states:
|
states:
|
||||||
@ -107,41 +103,33 @@
|
|||||||
? [filterPresets.state].flat()
|
? [filterPresets.state].flat()
|
||||||
: allJobStates,
|
: allJobStates,
|
||||||
startTime: filterPresets.startTime || { from: null, to: null, range: ""},
|
startTime: filterPresets.startTime || { from: null, to: null, range: ""},
|
||||||
tags: filterPresets.tags || [],
|
|
||||||
duration: filterPresets.duration || {
|
duration: filterPresets.duration || {
|
||||||
lessThan: null,
|
lessThan: null,
|
||||||
moreThan: null,
|
moreThan: null,
|
||||||
from: null,
|
from: null,
|
||||||
to: null,
|
to: null,
|
||||||
},
|
},
|
||||||
dbId: filterPresets.dbId || [],
|
tags: filterPresets.tags || [],
|
||||||
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 },
|
|
||||||
numNodes: filterPresets.numNodes || { from: null, to: null },
|
numNodes: filterPresets.numNodes || { from: null, to: null },
|
||||||
numHWThreads: filterPresets.numHWThreads || { from: null, to: null },
|
numHWThreads: filterPresets.numHWThreads || { from: null, to: null },
|
||||||
numAccelerators: filterPresets.numAccelerators || { 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 || [],
|
stats: filterPresets.stats || [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Opened States */
|
||||||
let isClusterOpen = $state(false)
|
let isClusterOpen = $state(false)
|
||||||
let isJobStatesOpen = $state(false)
|
let isJobStatesOpen = $state(false)
|
||||||
let isStartTimeOpen = $state(false)
|
let isStartTimeOpen = $state(false)
|
||||||
let isTagsOpen = $state(false)
|
|
||||||
let isDurationOpen = $state(false)
|
let isDurationOpen = $state(false)
|
||||||
let isEnergyOpen = $state(false)
|
let isTagsOpen = $state(false)
|
||||||
let isResourcesOpen = $state(false)
|
let isResourcesOpen = $state(false)
|
||||||
|
let isEnergyOpen = $state(false)
|
||||||
let isStatsOpen = $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.
|
// 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'
|
||||||
export function updateFilters(additionalFilters = null, force = false) {
|
export function updateFilters(additionalFilters = null, force = false) {
|
||||||
@ -155,10 +143,20 @@
|
|||||||
}
|
}
|
||||||
// Construct New Filter
|
// Construct New Filter
|
||||||
let items = [];
|
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.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.partition) items.push({ partition: { eq: filters.partition } });
|
||||||
if (filters.states.length != allJobStates.length)
|
if (filters.states.length != allJobStates?.length)
|
||||||
items.push({ state: filters.states });
|
items.push({ state: filters.states });
|
||||||
if (filters.startTime.from || filters.startTime.to)
|
if (filters.startTime.from || filters.startTime.to)
|
||||||
items.push({
|
items.push({
|
||||||
@ -168,7 +166,6 @@
|
|||||||
items.push({
|
items.push({
|
||||||
startTime: { range: filters.startTime.range },
|
startTime: { range: filters.startTime.range },
|
||||||
});
|
});
|
||||||
if (filters.tags.length != 0) items.push({ tags: filters.tags });
|
|
||||||
if (filters.duration.from || filters.duration.to)
|
if (filters.duration.from || filters.duration.to)
|
||||||
items.push({
|
items.push({
|
||||||
duration: { from: filters.duration.from, to: filters.duration.to },
|
duration: { from: filters.duration.from, to: filters.duration.to },
|
||||||
@ -177,21 +174,11 @@
|
|||||||
items.push({ duration: { from: 0, to: filters.duration.lessThan } });
|
items.push({ duration: { from: 0, to: filters.duration.lessThan } });
|
||||||
if (filters.duration.moreThan)
|
if (filters.duration.moreThan)
|
||||||
items.push({ duration: { from: filters.duration.moreThan, to: 604800 } }); // 7 days to include special jobs with long runtimes
|
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)
|
if (filters.tags.length != 0) items.push({ tags: filters.tags });
|
||||||
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.numNodes.from != null || filters.numNodes.to != null) {
|
if (filters.numNodes.from != null || filters.numNodes.to != null) {
|
||||||
items.push({
|
items.push({
|
||||||
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
|
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
|
||||||
});
|
});
|
||||||
isNodesModified = true;
|
|
||||||
}
|
}
|
||||||
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) {
|
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) {
|
||||||
items.push({
|
items.push({
|
||||||
@ -200,7 +187,6 @@
|
|||||||
to: filters.numHWThreads.to,
|
to: filters.numHWThreads.to,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
isHwthreadsModified = true;
|
|
||||||
}
|
}
|
||||||
if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) {
|
if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) {
|
||||||
items.push({
|
items.push({
|
||||||
@ -209,13 +195,12 @@
|
|||||||
to: filters.numAccelerators.to,
|
to: filters.numAccelerators.to,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
isAccsModified = true;
|
|
||||||
}
|
}
|
||||||
if (filters.user)
|
if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
|
||||||
items.push({ user: { [filters.userMatch]: filters.user } });
|
if (filters.energy.from || filters.energy.to)
|
||||||
if (filters.project)
|
items.push({
|
||||||
items.push({ project: { [filters.projectMatch]: filters.project } });
|
energy: { from: filters.energy.from, to: filters.energy.to },
|
||||||
if (filters.jobName) items.push({ jobName: { contains: filters.jobName } });
|
});
|
||||||
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 }} }) });
|
||||||
|
|
||||||
@ -228,20 +213,7 @@
|
|||||||
const dateToUnixEpoch = (rfc3339) => Math.floor(Date.parse(rfc3339) / 1000);
|
const dateToUnixEpoch = (rfc3339) => Math.floor(Date.parse(rfc3339) / 1000);
|
||||||
let opts = [];
|
let opts = [];
|
||||||
|
|
||||||
if (filters.cluster) opts.push(`cluster=${filters.cluster}`);
|
// Direct Filters
|
||||||
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}`)
|
|
||||||
}
|
|
||||||
if (filters.dbId.length != 0) {
|
if (filters.dbId.length != 0) {
|
||||||
for (let dbi of filters.dbId) {
|
for (let dbi of filters.dbId) {
|
||||||
opts.push(`dbId=${dbi}`);
|
opts.push(`dbId=${dbi}`);
|
||||||
@ -256,21 +228,12 @@
|
|||||||
}
|
}
|
||||||
if (filters.jobIdMatch != "eq")
|
if (filters.jobIdMatch != "eq")
|
||||||
opts.push(`jobIdMatch=${filters.jobIdMatch}`); // "eq" is default-case
|
opts.push(`jobIdMatch=${filters.jobIdMatch}`); // "eq" is default-case
|
||||||
for (let tag of filters.tags) opts.push(`tag=${tag}`);
|
if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`);
|
||||||
if (filters.duration.from && filters.duration.to)
|
if (filters.jobName) opts.push(`jobName=${filters.jobName}`);
|
||||||
opts.push(`duration=${filters.duration.from}-${filters.duration.to}`);
|
// View Filters
|
||||||
if (filters.duration.lessThan)
|
if (filters.project) opts.push(`project=${filters.project}`);
|
||||||
opts.push(`duration=0-${filters.duration.lessThan}`);
|
if (filters.project && filters.projectMatch != "contains") // "contains" is default-case
|
||||||
if (filters.duration.moreThan)
|
opts.push(`projectMatch=${filters.projectMatch}`);
|
||||||
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.user.length != 0)
|
if (filters.user.length != 0)
|
||||||
if (filters.userMatch != "in") {
|
if (filters.userMatch != "in") {
|
||||||
opts.push(`user=${filters.user}`);
|
opts.push(`user=${filters.user}`);
|
||||||
@ -279,16 +242,42 @@
|
|||||||
}
|
}
|
||||||
if (filters.userMatch != "contains") // "contains" is default-case
|
if (filters.userMatch != "contains") // "contains" is default-case
|
||||||
opts.push(`userMatch=${filters.userMatch}`);
|
opts.push(`userMatch=${filters.userMatch}`);
|
||||||
if (filters.project) opts.push(`project=${filters.project}`);
|
// Filter Modals
|
||||||
if (filters.project && filters.projectMatch != "contains") // "contains" is default-case
|
if (filters.cluster) opts.push(`cluster=${filters.cluster}`);
|
||||||
opts.push(`projectMatch=${filters.projectMatch}`);
|
if (filters.partition) opts.push(`partition=${filters.partition}`);
|
||||||
if (filters.jobName) opts.push(`jobName=${filters.jobName}`);
|
if (filters.states.length != allJobStates?.length)
|
||||||
if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`);
|
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)
|
if (filters.stats.length != 0)
|
||||||
for (let stat of filters.stats) {
|
for (let stat of filters.stats) {
|
||||||
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
|
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
|
||||||
}
|
}
|
||||||
|
// Build && Return
|
||||||
if (opts.length == 0 && window.location.search.length <= 1) return;
|
if (opts.length == 0 && window.location.search.length <= 1) return;
|
||||||
let newurl = `${window.location.pathname}?${opts.join("&")}`;
|
let newurl = `${window.location.pathname}?${opts.join("&")}`;
|
||||||
window.history.replaceState(null, "", newurl);
|
window.history.replaceState(null, "", newurl);
|
||||||
@ -309,36 +298,36 @@
|
|||||||
<DropdownItem disabled>{menuText}</DropdownItem>
|
<DropdownItem disabled>{menuText}</DropdownItem>
|
||||||
<DropdownItem divider />
|
<DropdownItem divider />
|
||||||
{/if}
|
{/if}
|
||||||
<DropdownItem on:click={() => (isClusterOpen = true)}>
|
<DropdownItem onclick={() => (isClusterOpen = true)}>
|
||||||
<Icon name="cpu" /> Cluster/Partition
|
<Icon name="cpu" /> Cluster/Partition
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem on:click={() => (isJobStatesOpen = true)}>
|
<DropdownItem onclick={() => (isJobStatesOpen = true)}>
|
||||||
<Icon name="gear-fill" /> Job States
|
<Icon name="gear-fill" /> Job States
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem on:click={() => (isStartTimeOpen = true)}>
|
<DropdownItem onclick={() => (isStartTimeOpen = true)}>
|
||||||
<Icon name="calendar-range" /> Start Time
|
<Icon name="calendar-range" /> Start Time
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem on:click={() => (isDurationOpen = true)}>
|
<DropdownItem onclick={() => (isDurationOpen = true)}>
|
||||||
<Icon name="stopwatch" /> Duration
|
<Icon name="stopwatch" /> Duration
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem on:click={() => (isTagsOpen = true)}>
|
<DropdownItem onclick={() => (isTagsOpen = true)}>
|
||||||
<Icon name="tags" /> Tags
|
<Icon name="tags" /> Tags
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem on:click={() => (isResourcesOpen = true)}>
|
<DropdownItem onclick={() => (isResourcesOpen = true)}>
|
||||||
<Icon name="hdd-stack" /> Resources
|
<Icon name="hdd-stack" /> Resources
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem on:click={() => (isEnergyOpen = true)}>
|
<DropdownItem onclick={() => (isEnergyOpen = true)}>
|
||||||
<Icon name="lightning-charge-fill" /> Energy
|
<Icon name="lightning-charge-fill" /> Energy
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem on:click={() => (isStatsOpen = true)}>
|
<DropdownItem onclick={() => (isStatsOpen = true)}>
|
||||||
<Icon name="bar-chart" on:click={() => (isStatsOpen = true)} /> Statistics
|
<Icon name="bar-chart" onclick={() => (isStatsOpen = true)} /> Statistics
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
{#if startTimeQuickSelect}
|
{#if startTimeQuickSelect}
|
||||||
<DropdownItem divider />
|
<DropdownItem divider />
|
||||||
<DropdownItem disabled>Start Time Quick Selection</DropdownItem>
|
<DropdownItem disabled>Start Time Quick Selection</DropdownItem>
|
||||||
{#each startTimeSelectOptions.filter((stso) => stso.range !== "") as { rangeLabel, range }}
|
{#each startTimeSelectOptions.filter((stso) => stso.range !== "") as { rangeLabel, range }}
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
filters.startTime.from = null
|
filters.startTime.from = null
|
||||||
filters.startTime.to = null
|
filters.startTime.to = null
|
||||||
filters.startTime.range = range;
|
filters.startTime.range = range;
|
||||||
@ -364,7 +353,7 @@
|
|||||||
{#if showFilter}
|
{#if showFilter}
|
||||||
<!-- SELECTED FILTER PILLS -->
|
<!-- SELECTED FILTER PILLS -->
|
||||||
{#if filters.cluster}
|
{#if filters.cluster}
|
||||||
<Info icon="cpu" on:click={() => (isClusterOpen = true)}>
|
<Info icon="cpu" onclick={() => (isClusterOpen = true)}>
|
||||||
{filters.cluster}
|
{filters.cluster}
|
||||||
{#if filters.partition}
|
{#if filters.partition}
|
||||||
({filters.partition})
|
({filters.partition})
|
||||||
@ -372,14 +361,14 @@
|
|||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.states.length != allJobStates.length}
|
{#if filters.states.length != allJobStates?.length}
|
||||||
<Info icon="gear-fill" on:click={() => (isJobStatesOpen = true)}>
|
<Info icon="gear-fill" onclick={() => (isJobStatesOpen = true)}>
|
||||||
{filters.states.join(", ")}
|
{filters.states.join(", ")}
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.startTime.from || filters.startTime.to}
|
{#if filters.startTime.from || filters.startTime.to}
|
||||||
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
<Info icon="calendar-range" onclick={() => (isStartTimeOpen = true)}>
|
||||||
{new Date(filters.startTime.from).toLocaleString()} - {new Date(
|
{new Date(filters.startTime.from).toLocaleString()} - {new Date(
|
||||||
filters.startTime.to,
|
filters.startTime.to,
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
@ -387,13 +376,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.startTime.range}
|
{#if filters.startTime.range}
|
||||||
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
<Info icon="calendar-range" onclick={() => (isStartTimeOpen = true)}>
|
||||||
{startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel }
|
{startTimeSelectOptions.find((stso) => stso.range === filters.startTime.range).rangeLabel }
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.duration.from || filters.duration.to}
|
{#if filters.duration.from || filters.duration.to}
|
||||||
<Info icon="stopwatch" on:click={() => (isDurationOpen = true)}>
|
<Info icon="stopwatch" onclick={() => (isDurationOpen = true)}>
|
||||||
{Math.floor(filters.duration.from / 3600)}h:{Math.floor(
|
{Math.floor(filters.duration.from / 3600)}h:{Math.floor(
|
||||||
(filters.duration.from % 3600) / 60,
|
(filters.duration.from % 3600) / 60,
|
||||||
)}m -
|
)}m -
|
||||||
@ -404,7 +393,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.duration.lessThan}
|
{#if filters.duration.lessThan}
|
||||||
<Info icon="stopwatch" on:click={() => (isDurationOpen = true)}>
|
<Info icon="stopwatch" onclick={() => (isDurationOpen = true)}>
|
||||||
Duration less than {Math.floor(
|
Duration less than {Math.floor(
|
||||||
filters.duration.lessThan / 3600,
|
filters.duration.lessThan / 3600,
|
||||||
)}h:{Math.floor((filters.duration.lessThan % 3600) / 60)}m
|
)}h:{Math.floor((filters.duration.lessThan % 3600) / 60)}m
|
||||||
@ -412,7 +401,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.duration.moreThan}
|
{#if filters.duration.moreThan}
|
||||||
<Info icon="stopwatch" on:click={() => (isDurationOpen = true)}>
|
<Info icon="stopwatch" onclick={() => (isDurationOpen = true)}>
|
||||||
Duration more than {Math.floor(
|
Duration more than {Math.floor(
|
||||||
filters.duration.moreThan / 3600,
|
filters.duration.moreThan / 3600,
|
||||||
)}h:{Math.floor((filters.duration.moreThan % 3600) / 60)}m
|
)}h:{Math.floor((filters.duration.moreThan % 3600) / 60)}m
|
||||||
@ -420,47 +409,45 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.tags.length != 0}
|
{#if filters.tags.length != 0}
|
||||||
<Info icon="tags" on:click={() => (isTagsOpen = true)}>
|
<Info icon="tags" onclick={() => (isTagsOpen = true)}>
|
||||||
{#each filters.tags as tagId}
|
{#each filters.tags as tagId}
|
||||||
{#key tagId}
|
<Tag id={tagId} clickable={false} />
|
||||||
<Tag id={tagId} clickable={false} />
|
|
||||||
{/key}
|
|
||||||
{/each}
|
{/each}
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/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}
|
{#if filters.numNodes.from != null || filters.numNodes.to != null}
|
||||||
<Info icon="hdd-stack" on:click={() => (isResourcesOpen = true)}>
|
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
||||||
{#if isNodesModified}
|
|
||||||
Nodes: {filters.numNodes.from} - {filters.numNodes.to}
|
Nodes: {filters.numNodes.from} - {filters.numNodes.to}
|
||||||
{/if}
|
</Info>
|
||||||
{#if isNodesModified && isHwthreadsModified},
|
{/if}
|
||||||
{/if}
|
|
||||||
{#if isHwthreadsModified}
|
{#if filters.numHWThreads.from != null || filters.numHWThreads.to != null}
|
||||||
|
<Info icon="cpu" onclick={() => (isResourcesOpen = true)}>
|
||||||
HWThreads: {filters.numHWThreads.from} - {filters.numHWThreads.to}
|
HWThreads: {filters.numHWThreads.from} - {filters.numHWThreads.to}
|
||||||
{/if}
|
</Info>
|
||||||
{#if (isNodesModified || isHwthreadsModified) && isAccsModified},
|
{/if}
|
||||||
{/if}
|
|
||||||
{#if isAccsModified}
|
{#if filters.numAccelerators.from != null || filters.numAccelerators.to != null}
|
||||||
|
<Info icon="gpu-card" onclick={() => (isResourcesOpen = true)}>
|
||||||
Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to}
|
Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to}
|
||||||
{/if}
|
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.node != null}
|
{#if filters.node != null}
|
||||||
<Info icon="hdd-stack" on:click={() => (isResourcesOpen = true)}>
|
<Info icon="hdd-stack" onclick={() => (isResourcesOpen = true)}>
|
||||||
Node{nodeMatchLabels[filters.nodeMatch]}: {filters.node}
|
Node{nodeMatchLabels[filters.nodeMatch]}: {filters.node}
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.energy.from || filters.energy.to}
|
{#if filters.energy.from || filters.energy.to}
|
||||||
<Info icon="lightning-charge-fill" on:click={() => (isEnergyOpen = true)}>
|
<Info icon="lightning-charge-fill" onclick={() => (isEnergyOpen = true)}>
|
||||||
Total Energy: {filters.energy.from} - {filters.energy.to}
|
Total Energy: {filters.energy.from} - {filters.energy.to}
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if filters.stats.length > 0}
|
{#if filters.stats.length > 0}
|
||||||
<Info icon="bar-chart" on:click={() => (isStatsOpen = true)}>
|
<Info icon="bar-chart" onclick={() => (isStatsOpen = true)}>
|
||||||
{filters.stats
|
{filters.stats
|
||||||
.map((stat) => `${stat.field}: ${stat.from} - ${stat.to}`)
|
.map((stat) => `${stat.field}: ${stat.from} - ${stat.to}`)
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
@ -469,69 +456,62 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Cluster
|
<Cluster
|
||||||
{disableClusterSelection}
|
|
||||||
bind:isOpen={isClusterOpen}
|
bind:isOpen={isClusterOpen}
|
||||||
bind:cluster={filters.cluster}
|
presetCluster={filters.cluster}
|
||||||
bind:partition={filters.partition}
|
presetPartition={filters.partition}
|
||||||
on:set-filter={() => updateFilters()}
|
{disableClusterSelection}
|
||||||
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<JobStates
|
<JobStates
|
||||||
bind:isOpen={isJobStatesOpen}
|
bind:isOpen={isJobStatesOpen}
|
||||||
bind:states={filters.states}
|
presetStates={filters.states}
|
||||||
on:set-filter={() => updateFilters()}
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StartTime
|
<StartTime
|
||||||
bind:isOpen={isStartTimeOpen}
|
bind:isOpen={isStartTimeOpen}
|
||||||
bind:from={filters.startTime.from}
|
presetStartTime={filters.startTime}
|
||||||
bind:to={filters.startTime.to}
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
bind:range={filters.startTime.range}
|
|
||||||
{startTimeSelectOptions}
|
|
||||||
on:set-filter={() => updateFilters()}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Duration
|
<Duration
|
||||||
bind:isOpen={isDurationOpen}
|
bind:isOpen={isDurationOpen}
|
||||||
bind:lessThan={filters.duration.lessThan}
|
presetDuration={filters.duration}
|
||||||
bind:moreThan={filters.duration.moreThan}
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
bind:from={filters.duration.from}
|
|
||||||
bind:to={filters.duration.to}
|
|
||||||
on:set-filter={() => updateFilters()}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tags
|
<Tags
|
||||||
bind:isOpen={isTagsOpen}
|
bind:isOpen={isTagsOpen}
|
||||||
bind:tags={filters.tags}
|
presetTags={filters.tags}
|
||||||
on:set-filter={() => updateFilters()}
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Resources
|
<Resources
|
||||||
cluster={filters.cluster}
|
|
||||||
bind:isOpen={isResourcesOpen}
|
bind:isOpen={isResourcesOpen}
|
||||||
bind:numNodes={filters.numNodes}
|
activeCluster={filters.cluster}
|
||||||
bind:numHWThreads={filters.numHWThreads}
|
presetNumNodes={filters.numNodes}
|
||||||
bind:numAccelerators={filters.numAccelerators}
|
presetNumHWThreads={filters.numHWThreads}
|
||||||
bind:namedNode={filters.node}
|
presetNumAccelerators={filters.numAccelerators}
|
||||||
bind:nodeMatch={filters.nodeMatch}
|
presetNamedNode={filters.node}
|
||||||
bind:isNodesModified
|
presetNodeMatch={filters.nodeMatch}
|
||||||
bind:isHwthreadsModified
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
bind:isAccsModified
|
|
||||||
on:set-filter={() => updateFilters()}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Statistics
|
|
||||||
bind:isOpen={isStatsOpen}
|
|
||||||
bind:stats={filters.stats}
|
|
||||||
on:set-filter={() => updateFilters()}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Energy
|
<Energy
|
||||||
bind:isOpen={isEnergyOpen}
|
bind:isOpen={isEnergyOpen}
|
||||||
bind:energy={filters.energy}
|
presetEnergy={filters.energy}
|
||||||
on:set-filter={() => updateFilters()}
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Statistics
|
||||||
|
bind:isOpen={isStatsOpen}
|
||||||
|
presetStats={filters.stats}
|
||||||
|
setFilter={(filter) => updateFilters(filter)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(.cc-dropdown-on-hover:hover .dropdown-menu) {
|
:global(.cc-dropdown-on-hover:hover .dropdown-menu) {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ListGroup,
|
ListGroup,
|
||||||
@ -24,18 +24,23 @@
|
|||||||
ModalFooter,
|
ModalFooter,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
const clusters = getContext("clusters"),
|
/* Svelte 5 Props */
|
||||||
initialized = getContext("initialized"),
|
let {
|
||||||
dispatch = createEventDispatcher();
|
isOpen = $bindable(false),
|
||||||
|
presetCluster = "",
|
||||||
|
presetPartition = "",
|
||||||
|
disableClusterSelection = false,
|
||||||
|
setFilter
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
/* State Init */
|
||||||
|
let pendingCluster = $state(presetCluster);
|
||||||
|
let pendingPartition = $state(presetPartition);
|
||||||
|
|
||||||
|
/* Derived Vars */
|
||||||
|
const clusters = $derived(getContext("clusters"));
|
||||||
|
const initialized = $derived(getContext("initialized"));
|
||||||
|
|
||||||
export let disableClusterSelection = false;
|
|
||||||
export let isModified = false;
|
|
||||||
export let isOpen = false;
|
|
||||||
export let cluster = null;
|
|
||||||
export let partition = null;
|
|
||||||
let pendingCluster = cluster,
|
|
||||||
pendingPartition = partition;
|
|
||||||
$: isModified = pendingCluster != cluster || pendingPartition != partition;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
@ -45,13 +50,13 @@
|
|||||||
<h4>Cluster</h4>
|
<h4>Cluster</h4>
|
||||||
{#if disableClusterSelection}
|
{#if disableClusterSelection}
|
||||||
<Button color="info" class="w-100 mb-2" disabled><b>Info: Cluster Selection Disabled in This View</b></Button>
|
<Button color="info" class="w-100 mb-2" disabled><b>Info: Cluster Selection Disabled in This View</b></Button>
|
||||||
<Button outline color="primary" class="w-100 mb-2" disabled><b>Selected Cluster: {cluster}</b></Button>
|
<Button outline color="primary" class="w-100 mb-2" disabled><b>Selected Cluster: {presetCluster}</b></Button>
|
||||||
{:else}
|
{:else}
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
<ListGroupItem
|
<ListGroupItem
|
||||||
disabled={disableClusterSelection}
|
disabled={disableClusterSelection}
|
||||||
active={pendingCluster == null}
|
active={pendingCluster == null}
|
||||||
on:click={() => ((pendingCluster = null), (pendingPartition = null))}
|
onclick={() => ((pendingCluster = null), (pendingPartition = null))}
|
||||||
>
|
>
|
||||||
Any Cluster
|
Any Cluster
|
||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
@ -59,7 +64,7 @@
|
|||||||
<ListGroupItem
|
<ListGroupItem
|
||||||
disabled={disableClusterSelection}
|
disabled={disableClusterSelection}
|
||||||
active={pendingCluster == cluster.name}
|
active={pendingCluster == cluster.name}
|
||||||
on:click={() => (
|
onclick={() => (
|
||||||
(pendingCluster = cluster.name), (pendingPartition = null)
|
(pendingCluster = cluster.name), (pendingPartition = null)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -75,14 +80,14 @@
|
|||||||
<ListGroup>
|
<ListGroup>
|
||||||
<ListGroupItem
|
<ListGroupItem
|
||||||
active={pendingPartition == null}
|
active={pendingPartition == null}
|
||||||
on:click={() => (pendingPartition = null)}
|
onclick={() => (pendingPartition = null)}
|
||||||
>
|
>
|
||||||
Any Partition
|
Any Partition
|
||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
{#each clusters.find((c) => c.name == pendingCluster).partitions as partition}
|
{#each clusters?.find((c) => c.name == pendingCluster)?.partitions as partition}
|
||||||
<ListGroupItem
|
<ListGroupItem
|
||||||
active={pendingPartition == partition}
|
active={pendingPartition == partition}
|
||||||
on:click={() => (pendingPartition = partition)}
|
onclick={() => (pendingPartition = partition)}
|
||||||
>
|
>
|
||||||
{partition}
|
{partition}
|
||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
@ -93,22 +98,20 @@
|
|||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
cluster = pendingCluster;
|
setFilter({ cluster: pendingCluster, partition: pendingPartition });
|
||||||
partition = pendingPartition;
|
|
||||||
dispatch("set-filter", { cluster, partition });
|
|
||||||
}}>Close & Apply</Button
|
}}>Close & Apply</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
cluster = pendingCluster = null;
|
pendingCluster = null;
|
||||||
partition = pendingPartition = null;
|
pendingPartition = null;
|
||||||
dispatch("set-filter", { cluster, partition });
|
setFilter({ cluster: pendingCluster, partition: pendingPartition})
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
@ -24,61 +23,81 @@
|
|||||||
ModalFooter,
|
ModalFooter,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
/* Svelte 5 Props */
|
||||||
|
let {
|
||||||
|
isOpen = $bindable(false),
|
||||||
|
presetDuration ={lessThan: null, moreThan: null, from: null, to: null},
|
||||||
|
setFilter
|
||||||
|
} = $props();
|
||||||
|
|
||||||
export let isOpen = false;
|
/* State Init */
|
||||||
export let lessThan = null;
|
let pendingDuration = $state(presetDuration);
|
||||||
export let moreThan = null;
|
let lessState = $state(secsToHoursAndMins(presetDuration?.lessThan));
|
||||||
export let from = null;
|
let moreState = $state(secsToHoursAndMins(presetDuration?.moreThan));
|
||||||
export let to = null;
|
let fromState = $state(secsToHoursAndMins(presetDuration?.from));
|
||||||
|
let toState = $state(secsToHoursAndMins(presetDuration?.to));
|
||||||
|
|
||||||
let pendingLessThan, pendingMoreThan, pendingFrom, pendingTo;
|
/* Derived Init */
|
||||||
let lessDisabled = false,
|
const lessDisabled = $derived(
|
||||||
moreDisabled = false,
|
moreState.hours !== 0 ||
|
||||||
betweenDisabled = false;
|
moreState.mins !== 0 ||
|
||||||
|
fromState.hours !== 0 ||
|
||||||
|
fromState.mins !== 0 ||
|
||||||
|
toState.hours !== 0 ||
|
||||||
|
toState.mins !== 0
|
||||||
|
);
|
||||||
|
|
||||||
function reset() {
|
const moreDisabled = $derived(
|
||||||
pendingLessThan =
|
lessState.hours !== 0 ||
|
||||||
lessThan == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(lessThan);
|
lessState.mins !== 0 ||
|
||||||
pendingMoreThan =
|
fromState.hours !== 0 ||
|
||||||
moreThan == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(moreThan);
|
fromState.mins !== 0 ||
|
||||||
pendingFrom =
|
toState.hours !== 0 ||
|
||||||
from == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(from);
|
toState.mins !== 0
|
||||||
pendingTo = to == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(to);
|
);
|
||||||
|
|
||||||
|
const betweenDisabled = $derived(
|
||||||
|
moreState.hours !== 0 ||
|
||||||
|
moreState.mins !== 0 ||
|
||||||
|
lessState.hours !== 0 ||
|
||||||
|
lessState.mins !== 0
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
function resetPending() {
|
||||||
|
pendingDuration = {
|
||||||
|
lessThan: null,
|
||||||
|
moreThan: null,
|
||||||
|
from: null,
|
||||||
|
to: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetStates() {
|
||||||
|
lessState = { hours: 0, mins: 0 }
|
||||||
|
moreState = { hours: 0, mins: 0 }
|
||||||
|
fromState = { hours: 0, mins: 0 }
|
||||||
|
toState = { hours: 0, mins: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
function secsToHoursAndMins(seconds) {
|
||||||
|
if (seconds) {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
seconds -= hours * 3600;
|
||||||
|
const mins = Math.floor(seconds / 60);
|
||||||
|
return { hours, mins };
|
||||||
|
} else {
|
||||||
|
return { hours: 0, mins: 0 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
function hoursAndMinsToSecs(hoursAndMins) {
|
||||||
|
if (hoursAndMins) {
|
||||||
function secsToHoursAndMins(duration) {
|
return hoursAndMins.hours * 3600 + hoursAndMins.mins * 60;
|
||||||
const hours = Math.floor(duration / 3600);
|
} else {
|
||||||
duration -= hours * 3600;
|
return 0
|
||||||
const mins = Math.floor(duration / 60);
|
}
|
||||||
return { hours, mins };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hoursAndMinsToSecs({ hours, mins }) {
|
|
||||||
return hours * 3600 + mins * 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
$: lessDisabled =
|
|
||||||
pendingMoreThan.hours !== 0 ||
|
|
||||||
pendingMoreThan.mins !== 0 ||
|
|
||||||
pendingFrom.hours !== 0 ||
|
|
||||||
pendingFrom.mins !== 0 ||
|
|
||||||
pendingTo.hours !== 0 ||
|
|
||||||
pendingTo.mins !== 0;
|
|
||||||
$: moreDisabled =
|
|
||||||
pendingLessThan.hours !== 0 ||
|
|
||||||
pendingLessThan.mins !== 0 ||
|
|
||||||
pendingFrom.hours !== 0 ||
|
|
||||||
pendingFrom.mins !== 0 ||
|
|
||||||
pendingTo.hours !== 0 ||
|
|
||||||
pendingTo.mins !== 0;
|
|
||||||
$: betweenDisabled =
|
|
||||||
pendingMoreThan.hours !== 0 ||
|
|
||||||
pendingMoreThan.mins !== 0 ||
|
|
||||||
pendingLessThan.hours !== 0 ||
|
|
||||||
pendingLessThan.mins !== 0;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
@ -92,7 +111,7 @@
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingMoreThan.hours}
|
bind:value={moreState.hours}
|
||||||
disabled={moreDisabled}
|
disabled={moreDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -107,7 +126,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="59"
|
max="59"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingMoreThan.mins}
|
bind:value={moreState.mins}
|
||||||
disabled={moreDisabled}
|
disabled={moreDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -126,7 +145,7 @@
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingLessThan.hours}
|
bind:value={lessState.hours}
|
||||||
disabled={lessDisabled}
|
disabled={lessDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -141,7 +160,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="59"
|
max="59"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingLessThan.mins}
|
bind:value={lessState.mins}
|
||||||
disabled={lessDisabled}
|
disabled={lessDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -160,7 +179,7 @@
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingFrom.hours}
|
bind:value={fromState.hours}
|
||||||
disabled={betweenDisabled}
|
disabled={betweenDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -175,7 +194,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="59"
|
max="59"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingFrom.mins}
|
bind:value={fromState.mins}
|
||||||
disabled={betweenDisabled}
|
disabled={betweenDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -192,7 +211,7 @@
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingTo.hours}
|
bind:value={toState.hours}
|
||||||
disabled={betweenDisabled}
|
disabled={betweenDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -207,7 +226,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="59"
|
max="59"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
bind:value={pendingTo.mins}
|
bind:value={toState.mins}
|
||||||
disabled={betweenDisabled}
|
disabled={betweenDisabled}
|
||||||
/>
|
/>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
@ -220,39 +239,32 @@
|
|||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
lessThan = hoursAndMinsToSecs(pendingLessThan);
|
pendingDuration.lessThan = hoursAndMinsToSecs(lessState);
|
||||||
moreThan = hoursAndMinsToSecs(pendingMoreThan);
|
pendingDuration.moreThan = hoursAndMinsToSecs(moreState);
|
||||||
from = hoursAndMinsToSecs(pendingFrom);
|
pendingDuration.from = hoursAndMinsToSecs(fromState);
|
||||||
to = hoursAndMinsToSecs(pendingTo);
|
pendingDuration.to = hoursAndMinsToSecs(toState);
|
||||||
dispatch("set-filter", { lessThan, moreThan, from, to });
|
setFilter({duration: pendingDuration});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close & Apply
|
Close & Apply
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="warning"
|
color="warning"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
lessThan = null;
|
resetStates();
|
||||||
moreThan = null;
|
|
||||||
from = null;
|
|
||||||
to = null;
|
|
||||||
reset();
|
|
||||||
}}>Reset Values</Button
|
}}>Reset Values</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
lessThan = null;
|
resetStates();
|
||||||
moreThan = null;
|
resetPending();
|
||||||
from = null;
|
setFilter({duration: pendingDuration});
|
||||||
to = null;
|
|
||||||
reset();
|
|
||||||
dispatch("set-filter", { lessThan, moreThan, from, to });
|
|
||||||
}}>Reset Filter</Button
|
}}>Reset Filter</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Modal,
|
Modal,
|
||||||
@ -20,49 +19,49 @@
|
|||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
/* Svelte 5 Props */
|
||||||
|
let {
|
||||||
|
isOpen = $bindable(false),
|
||||||
|
presetEnergy= {from: null, to: null},
|
||||||
|
setFilter,
|
||||||
|
} = $props();
|
||||||
|
|
||||||
const energyMaximum = 1000.0;
|
/* State Init */
|
||||||
|
let energyState = $state(presetEnergy);
|
||||||
|
|
||||||
export let isOpen = false;
|
|
||||||
export let energy= {from: null, to: null};
|
|
||||||
|
|
||||||
function resetRanges() {
|
|
||||||
energy.from = null
|
|
||||||
energy.to = null
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>Filter based on energy</ModalHeader>
|
<ModalHeader>Filter based on energy</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<h4>Total Job Energy (kWh)</h4>
|
<div class="mb-3">
|
||||||
<DoubleRangeSlider
|
<div class="mb-0"><b>Total Job Energy (kWh)</b></div>
|
||||||
on:change={({ detail }) => (
|
<DoubleRangeSlider
|
||||||
(energy.from = detail[0]), (energy.to = detail[1])
|
changeRange={(detail) => {
|
||||||
)}
|
energyState.from = detail[0];
|
||||||
min={0.0}
|
energyState.to = detail[1];
|
||||||
max={energyMaximum}
|
}}
|
||||||
firstSlider={energy?.from ? energy.from : 0.0}
|
sliderMin={0.0}
|
||||||
secondSlider={energy?.to ? energy.to : energyMaximum}
|
sliderMax={1000.0}
|
||||||
inputFieldFrom={energy?.from ? energy.from : null}
|
fromPreset={energyState?.from? energyState.from : 0.0}
|
||||||
inputFieldTo={energy?.to ? energy.to : null}
|
toPreset={energyState?.to? energyState.to : 1000.0}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
dispatch("set-filter", { energy });
|
setFilter({ energy: energyState });
|
||||||
}}>Close & Apply</Button
|
}}>Close & Apply</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
resetRanges();
|
energyState = {from: null, to: null};
|
||||||
dispatch("set-filter", { energy });
|
setFilter({ energy: energyState });
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||||
|
@ -4,16 +4,22 @@
|
|||||||
Properties:
|
Properties:
|
||||||
- `icon String`: Sveltestrap icon name
|
- `icon String`: Sveltestrap icon name
|
||||||
- `modified Bool?`: Optional if filter is modified [Default: false]
|
- `modified Bool?`: Optional if filter is modified [Default: false]
|
||||||
|
- `onclick Fn()`: Opens Modal on click
|
||||||
|
- `children Fn()?`: Internal prop, Svelte 5 version of <slot/>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Button, Icon } from "@sveltestrap/sveltestrap";
|
import { Button, Icon } from "@sveltestrap/sveltestrap";
|
||||||
|
/* Svelte 5 Props */
|
||||||
export let icon;
|
let { icon, modified, onclick, children } = $props();
|
||||||
export let modified = false;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button class="mr-2 mb-1" outline color={modified ? "warning" : "primary"} on:click>
|
<Button class="mr-2 mb-1" outline color={modified ? "warning" : "primary"} {onclick}>
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
<slot />
|
{#if children}
|
||||||
|
<!-- Note: Ignore '@' Error in IDE -->
|
||||||
|
{@render children()}
|
||||||
|
{:else}
|
||||||
|
<span>No content found</span>
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
- `const allJobStates [String]`: List of all available job states used in cc-backend
|
- `const allJobStates [String]`: List of all available job states used in cc-backend
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script context="module">
|
<script module>
|
||||||
export const allJobStates = [
|
export const allJobStates = [
|
||||||
"running",
|
"running",
|
||||||
"completed",
|
"completed",
|
||||||
@ -27,7 +27,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
/* Note: Ignore VSCode reported 'A component can only have one instance-level <script> element' error */
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ListGroup,
|
ListGroup,
|
||||||
@ -38,16 +39,16 @@
|
|||||||
ModalFooter,
|
ModalFooter,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
/* Svelte 5 Props */
|
||||||
|
let {
|
||||||
|
isOpen = $bindable(false),
|
||||||
|
presetStates = [...allJobStates],
|
||||||
|
setFilter
|
||||||
|
} = $props();
|
||||||
|
|
||||||
export let isModified = false;
|
/* State Init */
|
||||||
export let isOpen = false;
|
let pendingStates = $state([...presetStates]);
|
||||||
export let states = [...allJobStates];
|
|
||||||
|
|
||||||
let pendingStates = [...states];
|
|
||||||
$: isModified =
|
|
||||||
states.length != pendingStates.length ||
|
|
||||||
!states.every((state) => pendingStates.includes(state));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
@ -71,28 +72,25 @@
|
|||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={pendingStates.length == 0}
|
disabled={pendingStates.length == 0}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
states = [...pendingStates];
|
setFilter({ states: [...pendingStates] });
|
||||||
dispatch("set-filter", { states });
|
|
||||||
}}>Close & Apply</Button
|
}}>Close & Apply</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="warning"
|
color="warning"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
states = [...allJobStates];
|
|
||||||
pendingStates = [];
|
pendingStates = [];
|
||||||
}}>Deselect All</Button
|
}}>Deselect All</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
states = [...allJobStates];
|
|
||||||
pendingStates = [...allJobStates];
|
pendingStates = [...allJobStates];
|
||||||
dispatch("set-filter", { states });
|
setFilter({ states: [...pendingStates] });
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -2,14 +2,11 @@
|
|||||||
@component Filter sub-component for selecting job resources
|
@component Filter sub-component for selecting job resources
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `cluster Object?`: The currently selected cluster config [Default: null]
|
|
||||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||||
|
- `activeCluster String?`: The currently selected cluster name [Default: null]
|
||||||
- `numNodes Object?`: The currently selected numNodes filter [Default: {from:null, to:null}]
|
- `numNodes Object?`: The currently selected numNodes filter [Default: {from:null, to:null}]
|
||||||
- `numHWThreads Object?`: The currently selected numHWThreads filter [Default: {from:null, to:null}]
|
- `numHWThreads Object?`: The currently selected numHWThreads filter [Default: {from:null, to:null}]
|
||||||
- `numAccelerators Object?`: The currently selected numAccelerators filter [Default: {from:null, to:null}]
|
- `numAccelerators Object?`: The currently selected numAccelerators filter [Default: {from:null, to:null}]
|
||||||
- `isNodesModified Bool?`: Is the node filter modified [Default: false]
|
|
||||||
- `isHwthreadsModified Bool?`: Is the Hwthreads filter modified [Default: false]
|
|
||||||
- `isAccsModified Bool?`: Is the Accelerator filter modified [Default: false]
|
|
||||||
- `namedNode String?`: The currently selected single named node (= hostname) [Default: null]
|
- `namedNode String?`: The currently selected single named node (= hostname) [Default: null]
|
||||||
|
|
||||||
Events:
|
Events:
|
||||||
@ -17,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Modal,
|
Modal,
|
||||||
@ -28,27 +25,19 @@
|
|||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
||||||
|
|
||||||
const clusters = getContext("clusters"),
|
/* Svelte 5 Props*/
|
||||||
initialized = getContext("initialized"),
|
let {
|
||||||
dispatch = createEventDispatcher();
|
isOpen = $bindable(false),
|
||||||
|
activeCluster = null,
|
||||||
export let cluster = null;
|
presetNumNodes = { from: null, to: null },
|
||||||
export let isOpen = false;
|
presetNumHWThreads = { from: null, to: null },
|
||||||
export let numNodes = { from: null, to: null };
|
presetNumAccelerators = { from: null, to: null },
|
||||||
export let numHWThreads = { from: null, to: null };
|
presetNamedNode = null,
|
||||||
export let numAccelerators = { from: null, to: null };
|
presetNodeMatch = "eq",
|
||||||
export let isNodesModified = false;
|
setFilter
|
||||||
export let isHwthreadsModified = false;
|
} = $props()
|
||||||
export let isAccsModified = false;
|
|
||||||
export let namedNode = null;
|
|
||||||
export let nodeMatch = "eq"
|
|
||||||
|
|
||||||
let pendingNumNodes = numNodes,
|
|
||||||
pendingNumHWThreads = numHWThreads,
|
|
||||||
pendingNumAccelerators = numAccelerators,
|
|
||||||
pendingNamedNode = namedNode,
|
|
||||||
pendingNodeMatch = nodeMatch;
|
|
||||||
|
|
||||||
|
/* Const Init */
|
||||||
const nodeMatchLabels = {
|
const nodeMatchLabels = {
|
||||||
eq: "Equal To",
|
eq: "Equal To",
|
||||||
contains: "Contains",
|
contains: "Contains",
|
||||||
@ -85,75 +74,133 @@
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
let minNumNodes = 1,
|
/* State Init*/
|
||||||
maxNumNodes = 0,
|
// Counts
|
||||||
minNumHWThreads = 1,
|
let minNumNodes = $state(1);
|
||||||
maxNumHWThreads = 0,
|
let maxNumNodes = $state(0);
|
||||||
minNumAccelerators = 0,
|
let maxNumHWThreads = $state(0);
|
||||||
maxNumAccelerators = 0;
|
let maxNumAccelerators = $state(0);
|
||||||
$: {
|
// Pending
|
||||||
|
let pendingNumNodes = $state(presetNumNodes);
|
||||||
|
let pendingNumHWThreads = $state(presetNumHWThreads);
|
||||||
|
let pendingNumAccelerators = $state(presetNumAccelerators);
|
||||||
|
let pendingNamedNode = $state(presetNamedNode);
|
||||||
|
let pendingNodeMatch = $state(presetNodeMatch);
|
||||||
|
// Changable States
|
||||||
|
let nodesState = $state(presetNumNodes);
|
||||||
|
let threadState = $state(presetNumHWThreads);
|
||||||
|
let accState = $state(presetNumAccelerators);
|
||||||
|
|
||||||
|
/* Derived States */
|
||||||
|
const clusters = $derived(getContext("clusters"));
|
||||||
|
const initialized = $derived(getContext("initialized"));
|
||||||
|
// 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: 0, 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
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Reactive Effects | Svelte 5 onMount */
|
||||||
|
$effect(() => {
|
||||||
if ($initialized) {
|
if ($initialized) {
|
||||||
if (cluster != null) {
|
// 'hClusters' defined in templates/base.tmpl
|
||||||
const { subClusters } = clusters.find((c) => c.name == cluster);
|
if (activeCluster != null) {
|
||||||
const { filterRanges } = header.clusters.find((c) => c.name == cluster);
|
const { filterRanges } = hClusters.find((c) => c.name == activeCluster);
|
||||||
minNumNodes = filterRanges.numNodes.from;
|
minNumNodes = filterRanges.numNodes.from;
|
||||||
maxNumNodes = filterRanges.numNodes.to;
|
maxNumNodes = filterRanges.numNodes.to;
|
||||||
|
} else if (clusters.length > 0) {
|
||||||
|
for (let hc of hClusters) {
|
||||||
|
const { filterRanges } = hc;
|
||||||
|
minNumNodes = Math.min(minNumNodes, filterRanges.numNodes.from);
|
||||||
|
maxNumNodes = Math.max(maxNumNodes, filterRanges.numNodes.to);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($initialized) {
|
||||||
|
// 'hClusters' defined in templates/base.tmpl
|
||||||
|
if (activeCluster != null) {
|
||||||
|
const { subClusters } = clusters.find((c) => c.name == activeCluster);
|
||||||
maxNumAccelerators = findMaxNumAccels([{ subClusters }]);
|
maxNumAccelerators = findMaxNumAccels([{ subClusters }]);
|
||||||
maxNumHWThreads = findMaxNumHWThreadsPerNode([{ subClusters }]);
|
maxNumHWThreads = findMaxNumHWThreadsPerNode([{ subClusters }]);
|
||||||
} else if (clusters.length > 0) {
|
} else if (clusters.length > 0) {
|
||||||
const { filterRanges } = header.clusters[0];
|
|
||||||
minNumNodes = filterRanges.numNodes.from;
|
|
||||||
maxNumNodes = filterRanges.numNodes.to;
|
|
||||||
maxNumAccelerators = findMaxNumAccels(clusters);
|
maxNumAccelerators = findMaxNumAccels(clusters);
|
||||||
maxNumHWThreads = findMaxNumHWThreadsPerNode(clusters);
|
maxNumHWThreads = findMaxNumHWThreadsPerNode(clusters);
|
||||||
for (let cluster of header.clusters) {
|
|
||||||
const { filterRanges } = cluster;
|
|
||||||
minNumNodes = Math.min(minNumNodes, filterRanges.numNodes.from);
|
|
||||||
maxNumNodes = Math.max(maxNumNodes, filterRanges.numNodes.to);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
$: {
|
$effect(() => {
|
||||||
if (
|
if (
|
||||||
isOpen &&
|
|
||||||
$initialized &&
|
$initialized &&
|
||||||
pendingNumNodes.from == null &&
|
pendingNumNodes.from == null &&
|
||||||
pendingNumNodes.to == null
|
pendingNumNodes.to == null
|
||||||
) {
|
) {
|
||||||
pendingNumNodes = { from: 0, to: maxNumNodes };
|
nodesState = { from: 1, to: maxNumNodes };
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
$: {
|
$effect(() => {
|
||||||
if (
|
if (
|
||||||
isOpen &&
|
|
||||||
$initialized &&
|
$initialized &&
|
||||||
((pendingNumHWThreads.from == null && pendingNumHWThreads.to == null) ||
|
pendingNumHWThreads.from == null &&
|
||||||
isHwthreadsModified == false)
|
pendingNumHWThreads.to == null
|
||||||
) {
|
) {
|
||||||
pendingNumHWThreads = { from: 0, to: maxNumHWThreads };
|
threadState = { from: 1, to: maxNumHWThreads };
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
$: if (maxNumAccelerators != null && maxNumAccelerators > 1) {
|
$effect(() => {
|
||||||
if (
|
if (
|
||||||
isOpen &&
|
|
||||||
$initialized &&
|
$initialized &&
|
||||||
pendingNumAccelerators.from == null &&
|
pendingNumAccelerators.from == null &&
|
||||||
pendingNumAccelerators.to == null
|
pendingNumAccelerators.to == null
|
||||||
) {
|
) {
|
||||||
pendingNumAccelerators = { from: 0, to: maxNumAccelerators };
|
accState = { from: 0, to: maxNumAccelerators };
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
function setResources() {
|
||||||
|
if (nodesActive) {
|
||||||
|
pendingNumNodes = {...nodesState};
|
||||||
|
} else {
|
||||||
|
pendingNumNodes = { from: null, to: null };
|
||||||
|
};
|
||||||
|
if (threadActive) {
|
||||||
|
pendingNumHWThreads = {...threadState};
|
||||||
|
} else {
|
||||||
|
pendingNumHWThreads = { from: null, to: null };
|
||||||
|
};
|
||||||
|
if (accActive) {
|
||||||
|
pendingNumAccelerators = {...accState};
|
||||||
|
} else {
|
||||||
|
pendingNumAccelerators = { from: null, to: null };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetResources() {
|
||||||
|
pendingNumNodes = { from: null, to: null };
|
||||||
|
pendingNumHWThreads = { from: null, to: null };
|
||||||
|
pendingNumAccelerators = { from: null, to: null };
|
||||||
|
pendingNamedNode = null;
|
||||||
|
pendingNodeMatch = "eq";
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>Select number of utilized Resources</ModalHeader>
|
<ModalHeader>Select number of utilized Resources</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<h6>Named Node</h6>
|
<div><b>Named Node</b></div>
|
||||||
<div class="d-flex">
|
<div class="d-flex mb-3">
|
||||||
<Input type="text" class="w-75" bind:value={pendingNamedNode} />
|
<Input type="text" class="w-75" bind:value={pendingNamedNode} />
|
||||||
<div class="mx-1"></div>
|
<div class="mx-1"></div>
|
||||||
<Input type="select" class="w-25" bind:value={pendingNodeMatch}>
|
<Input type="select" class="w-25" bind:value={pendingNodeMatch}>
|
||||||
@ -164,82 +211,63 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<h6 style="margin-top: 1rem;">Number of Nodes</h6>
|
|
||||||
<DoubleRangeSlider
|
<div class="mb-3">
|
||||||
on:change={({ detail }) => {
|
<div class="mb-0"><b>Number of Nodes</b></div>
|
||||||
pendingNumNodes = { from: detail[0], to: detail[1] };
|
|
||||||
isNodesModified = true;
|
|
||||||
}}
|
|
||||||
min={minNumNodes}
|
|
||||||
max={maxNumNodes}
|
|
||||||
firstSlider={pendingNumNodes.from}
|
|
||||||
secondSlider={pendingNumNodes.to}
|
|
||||||
inputFieldFrom={pendingNumNodes.from}
|
|
||||||
inputFieldTo={pendingNumNodes.to}
|
|
||||||
/>
|
|
||||||
<h6 style="margin-top: 1rem;">
|
|
||||||
Number of HWThreads (Use for Single-Node Jobs)
|
|
||||||
</h6>
|
|
||||||
<DoubleRangeSlider
|
|
||||||
on:change={({ detail }) => {
|
|
||||||
pendingNumHWThreads = { from: detail[0], to: detail[1] };
|
|
||||||
isHwthreadsModified = true;
|
|
||||||
}}
|
|
||||||
min={minNumHWThreads}
|
|
||||||
max={maxNumHWThreads}
|
|
||||||
firstSlider={pendingNumHWThreads.from}
|
|
||||||
secondSlider={pendingNumHWThreads.to}
|
|
||||||
inputFieldFrom={pendingNumHWThreads.from}
|
|
||||||
inputFieldTo={pendingNumHWThreads.to}
|
|
||||||
/>
|
|
||||||
{#if maxNumAccelerators != null && maxNumAccelerators > 1}
|
|
||||||
<h6 style="margin-top: 1rem;">Number of Accelerators</h6>
|
|
||||||
<DoubleRangeSlider
|
<DoubleRangeSlider
|
||||||
on:change={({ detail }) => {
|
changeRange={(detail) => {
|
||||||
pendingNumAccelerators = { from: detail[0], to: detail[1] };
|
nodesState.from = detail[0];
|
||||||
isAccsModified = true;
|
nodesState.to = detail[1];
|
||||||
}}
|
}}
|
||||||
min={minNumAccelerators}
|
sliderMin={minNumNodes}
|
||||||
max={maxNumAccelerators}
|
sliderMax={maxNumNodes}
|
||||||
firstSlider={pendingNumAccelerators.from}
|
fromPreset={nodesState.from}
|
||||||
secondSlider={pendingNumAccelerators.to}
|
toPreset={nodesState.to}
|
||||||
inputFieldFrom={pendingNumAccelerators.from}
|
|
||||||
inputFieldTo={pendingNumAccelerators.to}
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="mb-0"><b>Number of HWThreads</b> (Use for Single-Node Jobs)</div>
|
||||||
|
<DoubleRangeSlider
|
||||||
|
changeRange={(detail) => {
|
||||||
|
threadState.from = detail[0];
|
||||||
|
threadState.to = detail[1];
|
||||||
|
}}
|
||||||
|
sliderMin={1}
|
||||||
|
sliderMax={maxNumHWThreads}
|
||||||
|
fromPreset={threadState.from}
|
||||||
|
toPreset={threadState.to}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if maxNumAccelerators != null && maxNumAccelerators > 1}
|
||||||
|
<div>
|
||||||
|
<div class="mb-0"><b>Number of Accelerators</b></div>
|
||||||
|
<DoubleRangeSlider
|
||||||
|
changeRange={(detail) => {
|
||||||
|
accState.from = detail[0];
|
||||||
|
accState.to = detail[1];
|
||||||
|
}}
|
||||||
|
sliderMin={0}
|
||||||
|
sliderMax={maxNumAccelerators}
|
||||||
|
fromPreset={accState.from}
|
||||||
|
toPreset={accState.to}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={pendingNumNodes.from == null || pendingNumNodes.to == null}
|
disabled={disableApply}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
pendingNumNodes = isNodesModified
|
setResources();
|
||||||
? pendingNumNodes
|
setFilter({
|
||||||
: { from: null, to: null };
|
numNodes: pendingNumNodes,
|
||||||
pendingNumHWThreads = isHwthreadsModified
|
numHWThreads: pendingNumHWThreads,
|
||||||
? pendingNumHWThreads
|
numAccelerators: pendingNumAccelerators,
|
||||||
: { from: null, to: null };
|
node: pendingNamedNode,
|
||||||
pendingNumAccelerators = isAccsModified
|
nodeMatch: pendingNodeMatch
|
||||||
? pendingNumAccelerators
|
|
||||||
: { from: null, to: null };
|
|
||||||
numNodes = { from: pendingNumNodes.from, to: pendingNumNodes.to };
|
|
||||||
numHWThreads = {
|
|
||||||
from: pendingNumHWThreads.from,
|
|
||||||
to: pendingNumHWThreads.to,
|
|
||||||
};
|
|
||||||
numAccelerators = {
|
|
||||||
from: pendingNumAccelerators.from,
|
|
||||||
to: pendingNumAccelerators.to,
|
|
||||||
};
|
|
||||||
namedNode = pendingNamedNode;
|
|
||||||
nodeMatch = pendingNodeMatch;
|
|
||||||
dispatch("set-filter", {
|
|
||||||
numNodes,
|
|
||||||
numHWThreads,
|
|
||||||
numAccelerators,
|
|
||||||
namedNode,
|
|
||||||
nodeMatch
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -247,36 +275,18 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
pendingNumNodes = { from: null, to: null };
|
resetResources();
|
||||||
pendingNumHWThreads = { from: null, to: null };
|
setFilter({
|
||||||
pendingNumAccelerators = { from: null, to: null };
|
numNodes: pendingNumNodes,
|
||||||
pendingNamedNode = null;
|
numHWThreads: pendingNumHWThreads,
|
||||||
pendingNodeMatch = null;
|
numAccelerators: pendingNumAccelerators,
|
||||||
numNodes = { from: pendingNumNodes.from, to: pendingNumNodes.to };
|
node: pendingNamedNode,
|
||||||
numHWThreads = {
|
nodeMatch: pendingNodeMatch
|
||||||
from: pendingNumHWThreads.from,
|
|
||||||
to: pendingNumHWThreads.to,
|
|
||||||
};
|
|
||||||
numAccelerators = {
|
|
||||||
from: pendingNumAccelerators.from,
|
|
||||||
to: pendingNumAccelerators.to,
|
|
||||||
};
|
|
||||||
isNodesModified = false;
|
|
||||||
isHwthreadsModified = false;
|
|
||||||
isAccsModified = false;
|
|
||||||
namedNode = pendingNamedNode;
|
|
||||||
nodeMatch = pendingNodeMatch;
|
|
||||||
dispatch("set-filter", {
|
|
||||||
numNodes,
|
|
||||||
numHWThreads,
|
|
||||||
numAccelerators,
|
|
||||||
namedNode,
|
|
||||||
nodeMatch
|
|
||||||
});
|
});
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -12,8 +12,19 @@
|
|||||||
- `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component
|
- `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<script module>
|
||||||
|
export 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"}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
/* Note: Ignore VSCode reported 'A component can only have one instance-level <script> element' error */
|
||||||
|
|
||||||
import { parse, format, sub } from "date-fns";
|
import { parse, format, sub } from "date-fns";
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
@ -26,44 +37,39 @@
|
|||||||
FormGroup,
|
FormGroup,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
/* Svelte 5 Props */
|
||||||
|
let {
|
||||||
export let isModified = false;
|
isOpen = $bindable(false),
|
||||||
export let isOpen = false;
|
presetStartTime = { from: null, to: null, range: "" },
|
||||||
export let from = null;
|
setFilter
|
||||||
export let to = null;
|
} = $props();
|
||||||
export let range = "";
|
|
||||||
export let startTimeSelectOptions;
|
|
||||||
|
|
||||||
|
/* Const Init */
|
||||||
const now = new Date(Date.now());
|
const now = new Date(Date.now());
|
||||||
const ago = sub(now, { months: 1 });
|
const ago = sub(now, { months: 1 });
|
||||||
const defaultFrom = {
|
const resetFrom = { date: format(ago, "yyyy-MM-dd"), time: format(ago, "HH:mm")};
|
||||||
date: format(ago, "yyyy-MM-dd"),
|
const resetTo = { date: format(now, "yyyy-MM-dd"), time: format(now, "HH:mm")};
|
||||||
time: format(ago, "HH:mm"),
|
|
||||||
};
|
|
||||||
const defaultTo = {
|
|
||||||
date: format(now, "yyyy-MM-dd"),
|
|
||||||
time: format(now, "HH:mm"),
|
|
||||||
};
|
|
||||||
|
|
||||||
$: pendingFrom = (from == null) ? defaultFrom : fromRFC3339(from)
|
/* State Init */
|
||||||
$: pendingTo = (to == null) ? defaultTo : fromRFC3339(to)
|
let pendingStartTime = $state(presetStartTime);
|
||||||
$: pendingRange = range
|
let fromState = $state(fromRFC3339(presetStartTime?.from, resetFrom));
|
||||||
|
let toState = $state(fromRFC3339(presetStartTime?.to, resetTo));
|
||||||
|
|
||||||
$: isModified =
|
/* Derived Init*/
|
||||||
(from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, "59")) &&
|
const rangeSelect = $derived(pendingStartTime?.range ? pendingStartTime.range : "")
|
||||||
(range != pendingRange) &&
|
|
||||||
!(
|
/* Functions */
|
||||||
from == null &&
|
function fromRFC3339(rfc3339, reset) {
|
||||||
pendingFrom.date == "0000-00-00" &&
|
if (rfc3339) {
|
||||||
pendingFrom.time == "00:00"
|
const parsedDate = new Date(rfc3339);
|
||||||
) &&
|
return {
|
||||||
!(
|
date: format(parsedDate, "yyyy-MM-dd"),
|
||||||
to == null &&
|
time: format(parsedDate, "HH:mm"),
|
||||||
pendingTo.date == "0000-00-00" &&
|
}
|
||||||
pendingTo.time == "00:00"
|
} else {
|
||||||
) &&
|
return reset
|
||||||
!( range == "" && pendingRange == "");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toRFC3339({ date, time }, secs = "00") {
|
function toRFC3339({ date, time }, secs = "00") {
|
||||||
const parsedDate = parse(
|
const parsedDate = parse(
|
||||||
@ -73,26 +79,18 @@
|
|||||||
);
|
);
|
||||||
return parsedDate.toISOString();
|
return parsedDate.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromRFC3339(rfc3339) {
|
|
||||||
const parsedDate = new Date(rfc3339);
|
|
||||||
return {
|
|
||||||
date: format(parsedDate, "yyyy-MM-dd"),
|
|
||||||
time: format(parsedDate, "HH:mm"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>Select Start Time</ModalHeader>
|
<ModalHeader>Select Start Time</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{#if range !== ""}
|
{#if rangeSelect !== ""}
|
||||||
<h4>Current Range</h4>
|
<h4>Current Range</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type ="select" bind:value={pendingRange} >
|
<Input type ="select" bind:value={pendingStartTime.range} >
|
||||||
{#each startTimeSelectOptions as { rangeLabel, range }}
|
{#each startTimeSelectOptions as { rangeLabel, range }}
|
||||||
<option label={rangeLabel} value={range}/>
|
<option label={rangeLabel} value={range}></option>
|
||||||
{/each}
|
{/each}
|
||||||
</Input>
|
</Input>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
@ -101,42 +99,41 @@
|
|||||||
<h4>From</h4>
|
<h4>From</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="date" bind:value={pendingFrom.date} disabled={pendingRange !== ""}/>
|
<Input type="date" bind:value={fromState.date} disabled={rangeSelect !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="time" bind:value={pendingFrom.time} disabled={pendingRange !== ""}/>
|
<Input type="time" bind:value={fromState.time} disabled={rangeSelect !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Row>
|
</Row>
|
||||||
<h4>To</h4>
|
<h4>To</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="date" bind:value={pendingTo.date} disabled={pendingRange !== ""}/>
|
<Input type="date" bind:value={toState.date} disabled={rangeSelect !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
<Input type="time" bind:value={pendingTo.time} disabled={pendingRange !== ""}/>
|
<Input type="time" bind:value={toState.time} disabled={rangeSelect !== ""}/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Row>
|
</Row>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
{#if pendingRange !== ""}
|
{#if rangeSelect !== ""}
|
||||||
<Button
|
<Button
|
||||||
color="warning"
|
color="warning"
|
||||||
disabled={pendingRange === ""}
|
disabled={rangeSelect === ""}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
pendingRange = ""
|
pendingStartTime.range = "";
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reset Range
|
Reset Range
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={pendingRange === ""}
|
disabled={rangeSelect === ""}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
from = null;
|
pendingStartTime.from = null;
|
||||||
to = null;
|
pendingStartTime.to = null;
|
||||||
range = pendingRange;
|
setFilter({ startTime: pendingStartTime });
|
||||||
dispatch("set-filter", { from, to, range });
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close & Apply Range
|
Close & Apply Range
|
||||||
@ -144,14 +141,14 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={pendingFrom.date == "0000-00-00" ||
|
disabled={fromState.date == "0000-00-00" ||
|
||||||
pendingTo.date == "0000-00-00"}
|
toState.date == "0000-00-00"}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
from = toRFC3339(pendingFrom);
|
pendingStartTime.from = toRFC3339(fromState);
|
||||||
to = toRFC3339(pendingTo, "59");
|
pendingStartTime.to = toRFC3339(toState, "59");
|
||||||
range = "";
|
pendingStartTime.range = "";
|
||||||
dispatch("set-filter", { from, to, range });
|
setFilter({ startTime: pendingStartTime });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close & Apply Dates
|
Close & Apply Dates
|
||||||
@ -159,14 +156,16 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
from = null;
|
fromState = resetFrom;
|
||||||
to = null;
|
toState = resetTo;
|
||||||
range = "";
|
pendingStartTime.from = null;
|
||||||
dispatch("set-filter", { from, to, range });
|
pendingStartTime.to = null;
|
||||||
|
pendingStartTime.range = "";
|
||||||
|
setFilter({ startTime: pendingStartTime });
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
@component Filter sub-component for selecting job statistics
|
@component Filter sub-component for selecting job statistics
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `isModified Bool?`: Is this filter component modified [Default: false]
|
|
||||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||||
- `stats [Object]?`: The currently selected statistics filter [Default: []]
|
- `stats [Object]?`: The currently selected statistics filter [Default: []]
|
||||||
|
|
||||||
@ -11,7 +10,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, getContext } from "svelte";
|
|
||||||
import { getStatsItems } from "../utils.js";
|
import { getStatsItems } from "../utils.js";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -22,75 +20,68 @@
|
|||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
||||||
|
|
||||||
const initialized = getContext("initialized"),
|
/* Svelte 5 Props */
|
||||||
dispatch = createEventDispatcher();
|
let {
|
||||||
|
isOpen = $bindable(),
|
||||||
|
presetStats,
|
||||||
|
setFilter
|
||||||
|
} = $props();
|
||||||
|
|
||||||
export let isModified = false;
|
/* Derived Init */
|
||||||
export let isOpen = false;
|
const availableStats = $derived(getStatsItems(presetStats));
|
||||||
export let stats = [];
|
|
||||||
|
|
||||||
let statistics = [];
|
|
||||||
|
|
||||||
function loadRanges(isInitialized) {
|
|
||||||
if (!isInitialized) return;
|
|
||||||
statistics = getStatsItems(stats);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
function resetRanges() {
|
function resetRanges() {
|
||||||
for (let st of statistics) {
|
for (let as of availableStats) {
|
||||||
st.enabled = false
|
as.enabled = false
|
||||||
st.from = 0
|
as.from = 0
|
||||||
st.to = st.peak
|
as.to = as.peak
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
$: isModified = !statistics.every((a) => {
|
|
||||||
let b = stats.find((s) => s.field == a.field);
|
|
||||||
if (b == null) return !a.enabled;
|
|
||||||
|
|
||||||
return a.from == b.from && a.to == b.to;
|
|
||||||
});
|
|
||||||
|
|
||||||
$: loadRanges($initialized);
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>Filter based on statistics</ModalHeader>
|
<ModalHeader>
|
||||||
|
<span>Filter based on statistics</span>
|
||||||
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{#each statistics as stat}
|
{#each availableStats as aStat}
|
||||||
<h4>{stat.text}</h4>
|
<div class="mb-3">
|
||||||
<DoubleRangeSlider
|
<div class="mb-0"><b>{aStat.text}</b></div>
|
||||||
on:change={({ detail }) => (
|
<DoubleRangeSlider
|
||||||
(stat.from = detail[0]), (stat.to = detail[1]), (stat.enabled = true)
|
changeRange={(detail) => {
|
||||||
)}
|
aStat.from = detail[0];
|
||||||
min={0}
|
aStat.to = detail[1];
|
||||||
max={stat.peak}
|
if (aStat.from == 0 && aStat.to == aStat.peak) {
|
||||||
firstSlider={stat.from}
|
aStat.enabled = false;
|
||||||
secondSlider={stat.to}
|
} else {
|
||||||
inputFieldFrom={stat.from}
|
aStat.enabled = true;
|
||||||
inputFieldTo={stat.to}
|
}
|
||||||
/>
|
}}
|
||||||
|
sliderMin={0.0}
|
||||||
|
sliderMax={aStat.peak}
|
||||||
|
fromPreset={aStat.from}
|
||||||
|
toPreset={aStat.to}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
stats = statistics.filter((stat) => stat.enabled);
|
setFilter({ stats: [...availableStats.filter((as) => as.enabled)] });
|
||||||
dispatch("set-filter", { stats });
|
|
||||||
}}>Close & Apply</Button
|
}}>Close & Apply</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
resetRanges();
|
resetRanges();
|
||||||
stats = [];
|
setFilter({stats: []});
|
||||||
dispatch("set-filter", { stats });
|
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ListGroup,
|
ListGroup,
|
||||||
@ -26,20 +26,20 @@
|
|||||||
import { fuzzySearchTags } from "../utils.js";
|
import { fuzzySearchTags } from "../utils.js";
|
||||||
import Tag from "../helper/Tag.svelte";
|
import Tag from "../helper/Tag.svelte";
|
||||||
|
|
||||||
const allTags = getContext("tags"),
|
/* Svelte 5 Props */
|
||||||
initialized = getContext("initialized"),
|
let {
|
||||||
dispatch = createEventDispatcher();
|
isOpen = $bindable(false),
|
||||||
|
presetTags = [],
|
||||||
|
setFilter
|
||||||
|
} = $props();
|
||||||
|
|
||||||
export let isModified = false;
|
/* Derived */
|
||||||
export let isOpen = false;
|
const allTags = $derived(getContext("tags"))
|
||||||
export let tags = [];
|
const initialized = $derived(getContext("initialized"))
|
||||||
|
|
||||||
let pendingTags = [...tags];
|
/* State Init */
|
||||||
$: isModified =
|
let pendingTags = $state(presetTags);
|
||||||
tags.length != pendingTags.length ||
|
let searchTerm = $state("");
|
||||||
!tags.every((tagId) => pendingTags.includes(tagId));
|
|
||||||
|
|
||||||
let searchTerm = "";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
color="danger"
|
color="danger"
|
||||||
on:click={() =>
|
onclick={() =>
|
||||||
(pendingTags = pendingTags.filter((id) => id != tag.id))}
|
(pendingTags = pendingTags.filter((id) => id != tag.id))}
|
||||||
>
|
>
|
||||||
<Icon name="dash-circle" />
|
<Icon name="dash-circle" />
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
color="success"
|
color="success"
|
||||||
on:click={() => (pendingTags = [...pendingTags, tag.id])}
|
onclick={() => (pendingTags = [...pendingTags, tag.id])}
|
||||||
>
|
>
|
||||||
<Icon name="plus-circle" />
|
<Icon name="plus-circle" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -81,21 +81,25 @@
|
|||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
tags = [...pendingTags];
|
setFilter({ tags: [...pendingTags] });
|
||||||
dispatch("set-filter", { tags });
|
|
||||||
}}>Close & Apply</Button
|
}}>Close & Apply</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="warning"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
isOpen = false;
|
|
||||||
tags = [];
|
|
||||||
pendingTags = [];
|
pendingTags = [];
|
||||||
dispatch("set-filter", { tags });
|
}}>Clear Selection</Button
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
onclick={() => {
|
||||||
|
isOpen = false;
|
||||||
|
pendingTags = [];
|
||||||
|
setFilter({ tags: [...pendingTags] });
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -10,15 +10,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Button, Icon, Input, InputGroup } from "@sveltestrap/sveltestrap";
|
import { Button, Icon, Input, InputGroup } from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
let refreshInterval = $state(null);
|
/* Svelte 5 Props */
|
||||||
let refreshIntervalId = null;
|
|
||||||
|
|
||||||
function refreshIntervalChanged() {
|
|
||||||
if (refreshIntervalId != null) clearInterval(refreshIntervalId);
|
|
||||||
if (refreshInterval == null) return;
|
|
||||||
refreshIntervalId = setInterval(() => dispatch("refresh"), refreshInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
initially = null,
|
initially = null,
|
||||||
onRefresh
|
onRefresh
|
||||||
@ -28,6 +20,19 @@
|
|||||||
refreshInterval = initially * 1000;
|
refreshInterval = initially * 1000;
|
||||||
refreshIntervalChanged();
|
refreshIntervalChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* State Init */
|
||||||
|
let refreshInterval = $state(null);
|
||||||
|
|
||||||
|
/* Var Init */
|
||||||
|
let refreshIntervalId = null;
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
function refreshIntervalChanged() {
|
||||||
|
if (refreshIntervalId != null) clearInterval(refreshIntervalId);
|
||||||
|
if (refreshInterval == null) return;
|
||||||
|
refreshIntervalId = setInterval(() => onRefresh(), refreshInterval);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
|
@ -9,21 +9,30 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
const allTags = getContext('tags'),
|
|
||||||
initialized = getContext('initialized')
|
/* Svelte 5 Props */
|
||||||
|
let {
|
||||||
|
id = null,
|
||||||
|
tag = null,
|
||||||
|
clickable = true
|
||||||
|
} = $props();
|
||||||
|
|
||||||
export let id = null
|
/* Derived */
|
||||||
export let tag = null
|
const allTags = $derived(getContext('tags'));
|
||||||
export let clickable = true
|
const initialized = $derived(getContext('initialized'));
|
||||||
|
|
||||||
if (tag != null && id == null)
|
/* Effects */
|
||||||
id = tag.id
|
$effect(() => {
|
||||||
|
if (tag != null && id == null)
|
||||||
|
id = tag.id
|
||||||
|
});
|
||||||
|
|
||||||
$: {
|
$effect(() => {
|
||||||
if ($initialized && tag == null)
|
if ($initialized && tag == null)
|
||||||
tag = allTags.find(tag => tag.id == id)
|
tag = allTags.find(tag => tag.id == id)
|
||||||
}
|
});
|
||||||
|
|
||||||
|
/* Function*/
|
||||||
function getScopeColor(scope) {
|
function getScopeColor(scope) {
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
case "admin":
|
case "admin":
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
import { InputGroup, Input, Button, Icon } from "@sveltestrap/sveltestrap";
|
import { InputGroup, Input, Button, Icon } from "@sveltestrap/sveltestrap";
|
||||||
import { scramble, scrambleNames } from "../utils.js";
|
import { scramble, scrambleNames } from "../utils.js";
|
||||||
|
|
||||||
// If page with this component has project preset, keep preset until reset
|
// Note: If page with this component has project preset, keep preset until reset
|
||||||
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
presetProject = "",
|
presetProject = "",
|
||||||
authlevel = null,
|
authlevel = null,
|
||||||
@ -22,14 +23,20 @@
|
|||||||
setFilter
|
setFilter
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let mode = $state(presetProject ? "jobName" : "project");
|
/* Const Init*/
|
||||||
let term = $state("");
|
const throttle = 500;
|
||||||
|
|
||||||
|
/* Var Init */
|
||||||
let user = "";
|
let user = "";
|
||||||
let project = presetProject ? presetProject : "";
|
let project = presetProject ? presetProject : "";
|
||||||
let jobName = "";
|
let jobName = "";
|
||||||
const throttle = 500;
|
let timeoutId = null;
|
||||||
|
|
||||||
|
/* State Init */
|
||||||
|
let mode = $state(presetProject ? "jobName" : "project");
|
||||||
|
let term = $state("");
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
function modeChanged() {
|
function modeChanged() {
|
||||||
if (mode == "user") {
|
if (mode == "user") {
|
||||||
project = presetProject ? presetProject : "";
|
project = presetProject ? presetProject : "";
|
||||||
@ -44,7 +51,6 @@
|
|||||||
termChanged(0);
|
termChanged(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeoutId = null;
|
|
||||||
// Compatibility: Handle "user role" and "no role" identically
|
// Compatibility: Handle "user role" and "no role" identically
|
||||||
function termChanged(sleep = throttle) {
|
function termChanged(sleep = throttle) {
|
||||||
if (roles && authlevel >= roles.manager) {
|
if (roles && authlevel >= roles.manager) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
Copyright (c) 2021 Michael Keller
|
Copyright (c) 2021 Michael Keller
|
||||||
Originally created by Michael Keller (https://github.com/mhkeller/svelte-double-range-slider)
|
Originally created by Michael Keller (https://github.com/mhkeller/svelte-double-range-slider)
|
||||||
Changes: remove dependency, text inputs, configurable value ranges, on:change event
|
Changes: remove dependency, text inputs, configurable value ranges, on:change event
|
||||||
|
Changes #2: Rewritten for Svelte 5, removed bodyHandler
|
||||||
-->
|
-->
|
||||||
<!--
|
<!--
|
||||||
@component Selector component to display range selections via min and max double-sliders
|
@component Selector component to display range selections via min and max double-sliders
|
||||||
@ -9,82 +10,80 @@ Changes: remove dependency, text inputs, configurable value ranges, on:change ev
|
|||||||
Properties:
|
Properties:
|
||||||
- min: Number
|
- min: Number
|
||||||
- max: Number
|
- max: Number
|
||||||
- firstSlider: Number (Starting position of slider #1)
|
- sliderHandleFrom: Number (Starting position of slider #1)
|
||||||
- secondSlider: Number (Starting position of slider #2)
|
- sliderHandleTo: Number (Starting position of slider #2)
|
||||||
|
|
||||||
Events:
|
Events:
|
||||||
- `change`: [Number, Number] (Positions of the two sliders)
|
- `change`: [Number, Number] (Positions of the two sliders)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
let {
|
||||||
|
sliderMin,
|
||||||
|
sliderMax,
|
||||||
|
fromPreset = 1,
|
||||||
|
toPreset = 100,
|
||||||
|
changeRange
|
||||||
|
} = $props();
|
||||||
|
|
||||||
export let min;
|
let pendingValues = $state([fromPreset, toPreset]);
|
||||||
export let max;
|
let sliderFrom = $state(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
|
||||||
export let firstSlider;
|
let sliderTo = $state(Math.min(((toPreset == null ? sliderMin : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
|
||||||
export let secondSlider;
|
let inputFieldFrom = $state(fromPreset.toString());
|
||||||
export let inputFieldFrom = 0;
|
let inputFieldTo = $state(toPreset.toString());
|
||||||
export let inputFieldTo = 0;
|
let leftHandle = $state();
|
||||||
|
let sliderMain = $state();
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
let values;
|
|
||||||
let start, end; /* Positions of sliders from 0 to 1 */
|
|
||||||
$: values = [firstSlider, secondSlider]; /* Avoid feedback loop */
|
|
||||||
$: start = Math.max(((firstSlider == null ? min : firstSlider) - min) / (max - min), 0);
|
|
||||||
$: end = Math.min(((secondSlider == null ? min : secondSlider) - min) / (max - min), 1);
|
|
||||||
|
|
||||||
let leftHandle;
|
|
||||||
let body;
|
|
||||||
let slider;
|
|
||||||
|
|
||||||
let timeoutId = null;
|
let timeoutId = null;
|
||||||
function queueChangeEvent() {
|
function queueChangeEvent() {
|
||||||
if (timeoutId !== null) {
|
if (timeoutId !== null) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId)
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
timeoutId = null;
|
timeoutId = null
|
||||||
|
changeRange(pendingValues);
|
||||||
// Show selection but avoid feedback loop
|
}, 100);
|
||||||
if (values[0] != null && inputFieldFrom != values[0].toString())
|
|
||||||
inputFieldFrom = values[0].toString();
|
|
||||||
if (values[1] != null && inputFieldTo != values[1].toString())
|
|
||||||
inputFieldTo = values[1].toString();
|
|
||||||
|
|
||||||
dispatch('change', values);
|
|
||||||
}, 250);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function updateStates(newValue, newPosition, target) {
|
||||||
values = [
|
if (target === 'from') {
|
||||||
Math.floor(min + start * (max - min)),
|
pendingValues[0] = isNaN(newValue) ? null : newValue;
|
||||||
Math.floor(min + end * (max - min))
|
inputFieldFrom = isNaN(newValue) ? null : newValue.toString();
|
||||||
];
|
sliderFrom = newPosition;
|
||||||
queueChangeEvent();
|
} else if (target === 'to') {
|
||||||
}
|
pendingValues[1] = isNaN(newValue) ? null : newValue;
|
||||||
|
inputFieldTo = isNaN(newValue) ? null : newValue.toString();
|
||||||
function inputChanged(idx, event) {
|
sliderTo = newPosition;
|
||||||
let val = Number.parseInt(event.target.value);
|
|
||||||
if (Number.isNaN(val) || val < min) {
|
|
||||||
event.target.classList.add('bad');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
values[idx] = val;
|
|
||||||
event.target.classList.remove('bad');
|
|
||||||
if (idx == 0)
|
|
||||||
start = clamp((val - min) / (max - min), 0., 1.);
|
|
||||||
else
|
|
||||||
end = clamp((val - min) / (max - min), 0., 1.);
|
|
||||||
|
|
||||||
queueChangeEvent();
|
queueChangeEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clamp(x, min, max) {
|
function rangeChanged (evt, target) {
|
||||||
return x < min
|
evt.preventDefault()
|
||||||
? min
|
evt.stopPropagation()
|
||||||
: (x < max ? x : max);
|
const { left, right } = sliderMain.getBoundingClientRect();
|
||||||
|
const parentWidth = right - left;
|
||||||
|
const newP = Math.min(Math.max((evt.detail.x - left) / parentWidth, 0), 1);
|
||||||
|
const newV = Math.floor(sliderMin + newP * (sliderMax - sliderMin));
|
||||||
|
updateStates(newV, newP, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputChanged(evt, target) {
|
||||||
|
evt.preventDefault()
|
||||||
|
evt.stopPropagation()
|
||||||
|
const newV = Number.parseInt(evt.target.value);
|
||||||
|
const newP = clamp((newV - sliderMin) / (sliderMax - sliderMin), 0., 1.)
|
||||||
|
updateStates(newV, newP, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(x, testMin, testMax) {
|
||||||
|
return x < testMin
|
||||||
|
? testMin
|
||||||
|
: (x > testMax
|
||||||
|
? testMax
|
||||||
|
: x
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function draggable(node) {
|
function draggable(node) {
|
||||||
@ -151,84 +150,38 @@ Changes: remove dependency, text inputs, configurable value ranges, on:change ev
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHandlePosition (which) {
|
|
||||||
return function (evt) {
|
|
||||||
const { left, right } = slider.getBoundingClientRect();
|
|
||||||
const parentWidth = right - left;
|
|
||||||
|
|
||||||
const p = Math.min(Math.max((evt.detail.x - left) / parentWidth, 0), 1);
|
|
||||||
|
|
||||||
if (which === 'start') {
|
|
||||||
start = p;
|
|
||||||
end = Math.max(end, p);
|
|
||||||
} else {
|
|
||||||
start = Math.min(p, start);
|
|
||||||
end = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setHandlesFromBody (evt) {
|
|
||||||
const { width } = body.getBoundingClientRect();
|
|
||||||
const { left, right } = slider.getBoundingClientRect();
|
|
||||||
|
|
||||||
const parentWidth = right - left;
|
|
||||||
|
|
||||||
const leftHandleLeft = leftHandle.getBoundingClientRect().left;
|
|
||||||
|
|
||||||
const pxStart = clamp((leftHandleLeft + evt.detail.dx) - left, 0, parentWidth - width);
|
|
||||||
const pxEnd = clamp(pxStart + width, width, parentWidth);
|
|
||||||
|
|
||||||
const pStart = pxStart / parentWidth;
|
|
||||||
const pEnd = pxEnd / parentWidth;
|
|
||||||
|
|
||||||
start = pStart;
|
|
||||||
end = pEnd;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="double-range-container">
|
<div class="double-range-container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<input class="form-control" type="text" placeholder="from..." bind:value={inputFieldFrom}
|
<input class="form-control" type="text" placeholder="from..." value={inputFieldFrom}
|
||||||
on:input={(e) => inputChanged(0, e)} />
|
oninput={(e) => inputChanged(e, 'from')} />
|
||||||
|
|
||||||
<span>Full Range: <b> {min} </b> - <b> {max} </b></span>
|
<span>Full Range: <b> {sliderMin} </b> - <b> {sliderMax} </b></span>
|
||||||
|
|
||||||
<input class="form-control" type="text" placeholder="to..." bind:value={inputFieldTo}
|
<input class="form-control" type="text" placeholder="to..." value={inputFieldTo}
|
||||||
on:input={(e) => inputChanged(1, e)} />
|
oninput={(e) => inputChanged(e, 'to')} />
|
||||||
</div>
|
</div>
|
||||||
<div class="slider" bind:this={slider}>
|
|
||||||
|
<div id="slider-active" class="slider" bind:this={sliderMain}>
|
||||||
<div
|
<div
|
||||||
class="body"
|
class="slider-body"
|
||||||
bind:this={body}
|
style="left: {100 * sliderFrom}%;right: {100 * (1 - sliderTo)}%;"
|
||||||
use:draggable
|
|
||||||
on:dragmove|preventDefault|stopPropagation="{setHandlesFromBody}"
|
|
||||||
style="
|
|
||||||
left: {100 * start}%;
|
|
||||||
right: {100 * (1 - end)}%;
|
|
||||||
"
|
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="handle"
|
class="slider-handle"
|
||||||
bind:this={leftHandle}
|
bind:this={leftHandle}
|
||||||
data-which="start"
|
data-which="from"
|
||||||
use:draggable
|
use:draggable
|
||||||
on:dragmove|preventDefault|stopPropagation="{setHandlePosition('start')}"
|
ondragmove={(e) => rangeChanged(e, 'from')}
|
||||||
style="
|
style="left: {100 * sliderFrom}%"
|
||||||
left: {100 * start}%
|
|
||||||
"
|
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
class="handle"
|
class="slider-handle"
|
||||||
data-which="end"
|
data-which="to"
|
||||||
use:draggable
|
use:draggable
|
||||||
on:dragmove|preventDefault|stopPropagation="{setHandlePosition('end')}"
|
ondragmove={(e) => rangeChanged(e, 'to')}
|
||||||
style="
|
style="left: {100 * sliderTo}%"
|
||||||
left: {100 * end}%
|
|
||||||
"
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -238,7 +191,8 @@ Changes: remove dependency, text inputs, configurable value ranges, on:change ev
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: -5px;
|
align-items: flex-end;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
.header :nth-child(2) {
|
.header :nth-child(2) {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
@ -249,17 +203,13 @@ Changes: remove dependency, text inputs, configurable value ranges, on:change ev
|
|||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.double-range-container .header input[type="text"].bad) {
|
|
||||||
color: #ff5c33;
|
|
||||||
border-color: #ff5c33;
|
|
||||||
}
|
|
||||||
|
|
||||||
.double-range-container {
|
.double-range-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
white-space: nowrap
|
white-space: nowrap;
|
||||||
|
margin-top: -4px;
|
||||||
}
|
}
|
||||||
.slider {
|
.slider {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -271,13 +221,13 @@ Changes: remove dependency, text inputs, configurable value ranges, on:change ev
|
|||||||
box-shadow: inset 0 7px 10px -5px #4a4a4a, inset 0 -1px 0px 0px #9c9c9c;
|
box-shadow: inset 0 7px 10px -5px #4a4a4a, inset 0 -1px 0px 0px #9c9c9c;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
.handle {
|
.slider-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
.handle:after {
|
.slider-handle:after {
|
||||||
content: ' ';
|
content: ' ';
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -291,11 +241,11 @@ Changes: remove dependency, text inputs, configurable value ranges, on:change ev
|
|||||||
/* .handle[data-which="end"]:after{
|
/* .handle[data-which="end"]:after{
|
||||||
transform: translate(-100%, -50%);
|
transform: translate(-100%, -50%);
|
||||||
} */
|
} */
|
||||||
.handle:active:after {
|
.slider-handle:active:after {
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
}
|
}
|
||||||
.body {
|
.slider-body {
|
||||||
top: 0;
|
top: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #34a1ff;
|
background-color: #34a1ff;
|
||||||
|
@ -6,6 +6,12 @@ const headerDomTarget = document.getElementById('svelte-header');
|
|||||||
if (headerDomTarget != null) {
|
if (headerDomTarget != null) {
|
||||||
mount(Header, {
|
mount(Header, {
|
||||||
target: headerDomTarget,
|
target: headerDomTarget,
|
||||||
props: { ...header },
|
props: { // { ...header },
|
||||||
|
username: hUsername,
|
||||||
|
authlevel: hAuthlevel,
|
||||||
|
clusters: hClusters,
|
||||||
|
subClusters: hSubClusters,
|
||||||
|
roles: hRoles,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,12 @@
|
|||||||
{{block "stylesheets" .}}
|
{{block "stylesheets" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<script>
|
<script>
|
||||||
const header = {
|
// Used for header.entrypoint.js mount and filters/Resources.svelte
|
||||||
"username": "{{ .User.Username }}",
|
const hUsername = {{ .User.Username }};
|
||||||
"authlevel": {{ .User.GetAuthLevel }},
|
const hAuthlevel = {{ .User.GetAuthLevel }};
|
||||||
"clusters": {{ .Clusters }},
|
const hClusters = {{ .Clusters }};
|
||||||
"subClusters": {{ .SubClusters }},
|
const hSubClusters = {{ .SubClusters }};
|
||||||
"roles": {{ .Roles }}
|
const hRoles = {{ .Roles }};
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="site">
|
<body class="site">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user