mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-04-03 06:27:30 +02:00
Restructure frontend svelte file src folder
- Goal: Dependency structure mirrored in file structure
This commit is contained in:
109
web/frontend/src/generic/filters/Cluster.svelte
Normal file
109
web/frontend/src/generic/filters/Cluster.svelte
Normal file
@@ -0,0 +1,109 @@
|
||||
<!--
|
||||
@component Filter sub-component for selecting cluster and subCluster
|
||||
|
||||
Properties:
|
||||
- `disableClusterSelection Bool?`: Is the selection disabled [Default: false]
|
||||
- `isModified Bool?`: Is this filter component modified [Default: false]
|
||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||
- `cluster String?`: The currently selected cluster [Default: null]
|
||||
- `partition String?`: The currently selected partition (i.e. subCluster) [Default: null]
|
||||
|
||||
Events:
|
||||
- `set-filter, {String?, String?}`: Set 'cluster, subCluster' filter in upstream component
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
import {
|
||||
Button,
|
||||
ListGroup,
|
||||
ListGroupItem,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
const clusters = getContext("clusters"),
|
||||
initialized = getContext("initialized"),
|
||||
dispatch = createEventDispatcher();
|
||||
|
||||
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>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
<ModalHeader>Select Cluster & Slurm Partition</ModalHeader>
|
||||
<ModalBody>
|
||||
{#if $initialized}
|
||||
<h4>Cluster</h4>
|
||||
<ListGroup>
|
||||
<ListGroupItem
|
||||
disabled={disableClusterSelection}
|
||||
active={pendingCluster == null}
|
||||
on:click={() => ((pendingCluster = null), (pendingPartition = null))}
|
||||
>
|
||||
Any Cluster
|
||||
</ListGroupItem>
|
||||
{#each clusters as cluster}
|
||||
<ListGroupItem
|
||||
disabled={disableClusterSelection}
|
||||
active={pendingCluster == cluster.name}
|
||||
on:click={() => (
|
||||
(pendingCluster = cluster.name), (pendingPartition = null)
|
||||
)}
|
||||
>
|
||||
{cluster.name}
|
||||
</ListGroupItem>
|
||||
{/each}
|
||||
</ListGroup>
|
||||
{/if}
|
||||
{#if $initialized && pendingCluster != null}
|
||||
<br />
|
||||
<h4>Partiton</h4>
|
||||
<ListGroup>
|
||||
<ListGroupItem
|
||||
active={pendingPartition == null}
|
||||
on:click={() => (pendingPartition = null)}
|
||||
>
|
||||
Any Partition
|
||||
</ListGroupItem>
|
||||
{#each clusters.find((c) => c.name == pendingCluster).partitions as partition}
|
||||
<ListGroupItem
|
||||
active={pendingPartition == partition}
|
||||
on:click={() => (pendingPartition = partition)}
|
||||
>
|
||||
{partition}
|
||||
</ListGroupItem>
|
||||
{/each}
|
||||
</ListGroup>
|
||||
{/if}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
cluster = pendingCluster;
|
||||
partition = pendingPartition;
|
||||
dispatch("set-filter", { cluster, partition });
|
||||
}}>Close & Apply</Button
|
||||
>
|
||||
<Button
|
||||
color="danger"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
cluster = pendingCluster = null;
|
||||
partition = pendingPartition = null;
|
||||
dispatch("set-filter", { cluster, partition });
|
||||
}}>Reset</Button
|
||||
>
|
||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
258
web/frontend/src/generic/filters/Duration.svelte
Normal file
258
web/frontend/src/generic/filters/Duration.svelte
Normal file
@@ -0,0 +1,258 @@
|
||||
<!--
|
||||
@component Filter sub-component for selecting job duration
|
||||
|
||||
Properties:
|
||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||
- `lessThan Number?`: Amount of seconds [Default: null]
|
||||
- `moreThan Number?`: Amount of seconds [Default: null]
|
||||
- `from Number?`: Epoch time in seconds [Default: null]
|
||||
- `to Number?`: Epoch time in seconds [Default: null]
|
||||
|
||||
Events:
|
||||
- `set-filter, {Number, Number, Number, Number}`: Set 'lessThan, moreThan, from, to' filter in upstream component
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let isOpen = false;
|
||||
export let lessThan = null;
|
||||
export let moreThan = null;
|
||||
export let from = null;
|
||||
export let to = null;
|
||||
|
||||
let pendingLessThan, pendingMoreThan, pendingFrom, pendingTo;
|
||||
let lessDisabled = false,
|
||||
moreDisabled = false,
|
||||
betweenDisabled = false;
|
||||
|
||||
function reset() {
|
||||
pendingLessThan =
|
||||
lessThan == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(lessThan);
|
||||
pendingMoreThan =
|
||||
moreThan == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(moreThan);
|
||||
pendingFrom =
|
||||
from == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(from);
|
||||
pendingTo = to == null ? { hours: 0, mins: 0 } : secsToHoursAndMins(to);
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
function secsToHoursAndMins(duration) {
|
||||
const hours = Math.floor(duration / 3600);
|
||||
duration -= hours * 3600;
|
||||
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>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
<ModalHeader>Select Job Duration</ModalHeader>
|
||||
<ModalBody>
|
||||
<h4>Duration more than</h4>
|
||||
<Row>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
class="form-control"
|
||||
bind:value={pendingMoreThan.hours}
|
||||
disabled={moreDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">h</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
class="form-control"
|
||||
bind:value={pendingMoreThan.mins}
|
||||
disabled={moreDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">m</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
|
||||
<h4>Duration less than</h4>
|
||||
<Row>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
class="form-control"
|
||||
bind:value={pendingLessThan.hours}
|
||||
disabled={lessDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">h</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
class="form-control"
|
||||
bind:value={pendingLessThan.mins}
|
||||
disabled={lessDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">m</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
|
||||
<h4>Duration between</h4>
|
||||
<Row>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
class="form-control"
|
||||
bind:value={pendingFrom.hours}
|
||||
disabled={betweenDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">h</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
class="form-control"
|
||||
bind:value={pendingFrom.mins}
|
||||
disabled={betweenDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">m</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<h4>and</h4>
|
||||
<Row>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
class="form-control"
|
||||
bind:value={pendingTo.hours}
|
||||
disabled={betweenDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">h</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col>
|
||||
<div class="input-group mb-2 mr-sm-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
class="form-control"
|
||||
bind:value={pendingTo.mins}
|
||||
disabled={betweenDisabled}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">m</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
lessThan = hoursAndMinsToSecs(pendingLessThan);
|
||||
moreThan = hoursAndMinsToSecs(pendingMoreThan);
|
||||
from = hoursAndMinsToSecs(pendingFrom);
|
||||
to = hoursAndMinsToSecs(pendingTo);
|
||||
dispatch("set-filter", { lessThan, moreThan, from, to });
|
||||
}}
|
||||
>
|
||||
Close & Apply
|
||||
</Button>
|
||||
<Button
|
||||
color="warning"
|
||||
on:click={() => {
|
||||
lessThan = null;
|
||||
moreThan = null;
|
||||
from = null;
|
||||
to = null;
|
||||
reset();
|
||||
}}>Reset Values</Button
|
||||
>
|
||||
<Button
|
||||
color="danger"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
lessThan = null;
|
||||
moreThan = null;
|
||||
from = null;
|
||||
to = null;
|
||||
reset();
|
||||
dispatch("set-filter", { lessThan, moreThan, from, to });
|
||||
}}>Reset Filter</Button
|
||||
>
|
||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
19
web/frontend/src/generic/filters/InfoBox.svelte
Normal file
19
web/frontend/src/generic/filters/InfoBox.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<!--
|
||||
@component Info pill displayed for active filters
|
||||
|
||||
Properties:
|
||||
- `icon String`: Sveltestrap icon name
|
||||
- `modified Bool?`: Optional if filter is modified [Default: false]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Button, Icon } from "@sveltestrap/sveltestrap";
|
||||
|
||||
export let icon;
|
||||
export let modified = false;
|
||||
</script>
|
||||
|
||||
<Button outline color={modified ? "warning" : "primary"} on:click>
|
||||
<Icon name={icon} />
|
||||
<slot />
|
||||
</Button>
|
||||
91
web/frontend/src/generic/filters/JobStates.svelte
Normal file
91
web/frontend/src/generic/filters/JobStates.svelte
Normal file
@@ -0,0 +1,91 @@
|
||||
<!--
|
||||
@component Filter sub-component for selecting job states
|
||||
|
||||
Properties:
|
||||
- `isModified Bool?`: Is this filter component modified [Default: false]
|
||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||
- `states [String]?`: The currently selected states [Default: [...allJobStates]]
|
||||
|
||||
Events:
|
||||
- `set-filter, {[String]}`: Set 'states' filter in upstream component
|
||||
|
||||
Exported:
|
||||
- `const allJobStates [String]`: List of all available job states used in cc-backend
|
||||
-->
|
||||
|
||||
<script context="module">
|
||||
export const allJobStates = [
|
||||
"running",
|
||||
"completed",
|
||||
"failed",
|
||||
"cancelled",
|
||||
"stopped",
|
||||
"timeout",
|
||||
"preempted",
|
||||
"out_of_memory",
|
||||
];
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import {
|
||||
Button,
|
||||
ListGroup,
|
||||
ListGroupItem,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let isModified = false;
|
||||
export let isOpen = false;
|
||||
export let states = [...allJobStates];
|
||||
|
||||
let pendingStates = [...states];
|
||||
$: isModified =
|
||||
states.length != pendingStates.length ||
|
||||
!states.every((state) => pendingStates.includes(state));
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
<ModalHeader>Select Job States</ModalHeader>
|
||||
<ModalBody>
|
||||
<ListGroup>
|
||||
{#each allJobStates as state}
|
||||
<ListGroupItem>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:group={pendingStates}
|
||||
name="flavours"
|
||||
value={state}
|
||||
/>
|
||||
{state}
|
||||
</ListGroupItem>
|
||||
{/each}
|
||||
</ListGroup>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={pendingStates.length == 0}
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
states = [...pendingStates];
|
||||
dispatch("set-filter", { states });
|
||||
}}>Close & Apply</Button
|
||||
>
|
||||
<Button
|
||||
color="danger"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
states = [...allJobStates];
|
||||
pendingStates = [...allJobStates];
|
||||
dispatch("set-filter", { states });
|
||||
}}>Reset</Button
|
||||
>
|
||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
259
web/frontend/src/generic/filters/Resources.svelte
Normal file
259
web/frontend/src/generic/filters/Resources.svelte
Normal file
@@ -0,0 +1,259 @@
|
||||
<!--
|
||||
@component Filter sub-component for selecting job resources
|
||||
|
||||
Properties:
|
||||
- `cluster Object?`: The currently selected cluster config [Default: null]
|
||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||
- `numNodes Object?`: The currently selected numNodes filter [Default: {from:null, to:null}]
|
||||
- `numHWThreads Object?`: The currently selected numHWTreads 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]
|
||||
- `isHwtreadsModified 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]
|
||||
|
||||
Events:
|
||||
- `set-filter, {Object, Object, Object, String}`: Set 'numNodes, numHWThreads, numAccelerators, namedNode' filter in upstream component
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
||||
|
||||
const clusters = getContext("clusters"),
|
||||
initialized = getContext("initialized"),
|
||||
dispatch = createEventDispatcher();
|
||||
|
||||
export let cluster = null;
|
||||
export let isOpen = false;
|
||||
export let numNodes = { from: null, to: null };
|
||||
export let numHWThreads = { from: null, to: null };
|
||||
export let numAccelerators = { from: null, to: null };
|
||||
export let isNodesModified = false;
|
||||
export let isHwthreadsModified = false;
|
||||
export let isAccsModified = false;
|
||||
export let namedNode = null;
|
||||
|
||||
let pendingNumNodes = numNodes,
|
||||
pendingNumHWThreads = numHWThreads,
|
||||
pendingNumAccelerators = numAccelerators,
|
||||
pendingNamedNode = namedNode;
|
||||
|
||||
const findMaxNumAccels = (clusters) =>
|
||||
clusters.reduce(
|
||||
(max, cluster) =>
|
||||
Math.max(
|
||||
max,
|
||||
cluster.subClusters.reduce(
|
||||
(max, sc) => Math.max(max, sc.topology.accelerators?.length || 0),
|
||||
0,
|
||||
),
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
// Limited to Single-Node Thread Count
|
||||
const findMaxNumHWTreadsPerNode = (clusters) =>
|
||||
clusters.reduce(
|
||||
(max, cluster) =>
|
||||
Math.max(
|
||||
max,
|
||||
cluster.subClusters.reduce(
|
||||
(max, sc) =>
|
||||
Math.max(
|
||||
max,
|
||||
sc.threadsPerCore * sc.coresPerSocket * sc.socketsPerNode || 0,
|
||||
),
|
||||
0,
|
||||
),
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
let minNumNodes = 1,
|
||||
maxNumNodes = 0,
|
||||
minNumHWThreads = 1,
|
||||
maxNumHWThreads = 0,
|
||||
minNumAccelerators = 0,
|
||||
maxNumAccelerators = 0;
|
||||
$: {
|
||||
if ($initialized) {
|
||||
if (cluster != null) {
|
||||
const { subClusters } = clusters.find((c) => c.name == cluster);
|
||||
const { filterRanges } = header.clusters.find((c) => c.name == cluster);
|
||||
minNumNodes = filterRanges.numNodes.from;
|
||||
maxNumNodes = filterRanges.numNodes.to;
|
||||
maxNumAccelerators = findMaxNumAccels([{ subClusters }]);
|
||||
maxNumHWThreads = findMaxNumHWTreadsPerNode([{ subClusters }]);
|
||||
} else if (clusters.length > 0) {
|
||||
const { filterRanges } = header.clusters[0];
|
||||
minNumNodes = filterRanges.numNodes.from;
|
||||
maxNumNodes = filterRanges.numNodes.to;
|
||||
maxNumAccelerators = findMaxNumAccels(clusters);
|
||||
maxNumHWThreads = findMaxNumHWTreadsPerNode(clusters);
|
||||
for (let cluster of header.clusters) {
|
||||
const { filterRanges } = cluster;
|
||||
minNumNodes = Math.min(minNumNodes, filterRanges.numNodes.from);
|
||||
maxNumNodes = Math.max(maxNumNodes, filterRanges.numNodes.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (
|
||||
isOpen &&
|
||||
$initialized &&
|
||||
pendingNumNodes.from == null &&
|
||||
pendingNumNodes.to == null
|
||||
) {
|
||||
pendingNumNodes = { from: 0, to: maxNumNodes };
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (
|
||||
isOpen &&
|
||||
$initialized &&
|
||||
((pendingNumHWThreads.from == null && pendingNumHWThreads.to == null) ||
|
||||
isHwthreadsModified == false)
|
||||
) {
|
||||
pendingNumHWThreads = { from: 0, to: maxNumHWThreads };
|
||||
}
|
||||
}
|
||||
|
||||
$: if (maxNumAccelerators != null && maxNumAccelerators > 1) {
|
||||
if (
|
||||
isOpen &&
|
||||
$initialized &&
|
||||
pendingNumAccelerators.from == null &&
|
||||
pendingNumAccelerators.to == null
|
||||
) {
|
||||
pendingNumAccelerators = { from: 0, to: maxNumAccelerators };
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
<ModalHeader>Select number of utilized Resources</ModalHeader>
|
||||
<ModalBody>
|
||||
<h6>Named Node</h6>
|
||||
<input type="text" class="form-control" bind:value={pendingNamedNode} />
|
||||
<h6 style="margin-top: 1rem;">Number of Nodes</h6>
|
||||
<DoubleRangeSlider
|
||||
on:change={({ detail }) => {
|
||||
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
|
||||
on:change={({ detail }) => {
|
||||
pendingNumAccelerators = { from: detail[0], to: detail[1] };
|
||||
isAccsModified = true;
|
||||
}}
|
||||
min={minNumAccelerators}
|
||||
max={maxNumAccelerators}
|
||||
firstSlider={pendingNumAccelerators.from}
|
||||
secondSlider={pendingNumAccelerators.to}
|
||||
inputFieldFrom={pendingNumAccelerators.from}
|
||||
inputFieldTo={pendingNumAccelerators.to}
|
||||
/>
|
||||
{/if}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={pendingNumNodes.from == null || pendingNumNodes.to == null}
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
pendingNumNodes = isNodesModified
|
||||
? pendingNumNodes
|
||||
: { from: null, to: null };
|
||||
pendingNumHWThreads = isHwthreadsModified
|
||||
? pendingNumHWThreads
|
||||
: { from: null, to: null };
|
||||
pendingNumAccelerators = isAccsModified
|
||||
? 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;
|
||||
dispatch("set-filter", {
|
||||
numNodes,
|
||||
numHWThreads,
|
||||
numAccelerators,
|
||||
namedNode,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Close & Apply
|
||||
</Button>
|
||||
<Button
|
||||
color="danger"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
pendingNumNodes = { from: null, to: null };
|
||||
pendingNumHWThreads = { from: null, to: null };
|
||||
pendingNumAccelerators = { from: null, to: null };
|
||||
pendingNamedNode = null;
|
||||
numNodes = { from: pendingNumNodes.from, to: pendingNumNodes.to };
|
||||
numHWThreads = {
|
||||
from: pendingNumHWThreads.from,
|
||||
to: pendingNumHWThreads.to,
|
||||
};
|
||||
numAccelerators = {
|
||||
from: pendingNumAccelerators.from,
|
||||
to: pendingNumAccelerators.to,
|
||||
};
|
||||
isNodesModified = false;
|
||||
isHwthreadsModified = false;
|
||||
isAccsModified = false;
|
||||
namedNode = pendingNamedNode;
|
||||
dispatch("set-filter", {
|
||||
numNodes,
|
||||
numHWThreads,
|
||||
numAccelerators,
|
||||
namedNode,
|
||||
});
|
||||
}}>Reset</Button
|
||||
>
|
||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
134
web/frontend/src/generic/filters/StartTime.svelte
Normal file
134
web/frontend/src/generic/filters/StartTime.svelte
Normal file
@@ -0,0 +1,134 @@
|
||||
<!--
|
||||
@component Filter sub-component for selecting job starttime
|
||||
|
||||
Properties:
|
||||
- `isModified Bool?`: Is this filter component modified [Default: false]
|
||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||
- `from Object?`: The currently selected from startime [Default: null]
|
||||
- `to Object?`: The currently selected to starttime (i.e. subCluster) [Default: null]
|
||||
|
||||
Events:
|
||||
- `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { parse, format, sub } from "date-fns";
|
||||
import {
|
||||
Row,
|
||||
Button,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
FormGroup,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let isModified = false;
|
||||
export let isOpen = false;
|
||||
export let from = null;
|
||||
export let to = null;
|
||||
|
||||
let pendingFrom, pendingTo;
|
||||
|
||||
const now = new Date(Date.now());
|
||||
const ago = sub(now, { months: 1 });
|
||||
const defaultFrom = {
|
||||
date: format(ago, "yyyy-MM-dd"),
|
||||
time: format(ago, "HH:mm"),
|
||||
};
|
||||
const defaultTo = {
|
||||
date: format(now, "yyyy-MM-dd"),
|
||||
time: format(now, "HH:mm"),
|
||||
};
|
||||
|
||||
function reset() {
|
||||
pendingFrom = from == null ? defaultFrom : fromRFC3339(from);
|
||||
pendingTo = to == null ? defaultTo : fromRFC3339(to);
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
function toRFC3339({ date, time }, secs = "00") {
|
||||
const parsedDate = parse(
|
||||
date + " " + time + ":" + secs,
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
new Date(),
|
||||
);
|
||||
return parsedDate.toISOString();
|
||||
}
|
||||
|
||||
function fromRFC3339(rfc3339) {
|
||||
const parsedDate = new Date(rfc3339);
|
||||
return {
|
||||
date: format(parsedDate, "yyyy-MM-dd"),
|
||||
time: format(parsedDate, "HH:mm"),
|
||||
};
|
||||
}
|
||||
|
||||
$: isModified =
|
||||
(from != toRFC3339(pendingFrom) || to != toRFC3339(pendingTo, "59")) &&
|
||||
!(
|
||||
from == null &&
|
||||
pendingFrom.date == "0000-00-00" &&
|
||||
pendingFrom.time == "00:00"
|
||||
) &&
|
||||
!(
|
||||
to == null &&
|
||||
pendingTo.date == "0000-00-00" &&
|
||||
pendingTo.time == "00:00"
|
||||
);
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
<ModalHeader>Select Start Time</ModalHeader>
|
||||
<ModalBody>
|
||||
<h4>From</h4>
|
||||
<Row>
|
||||
<FormGroup class="col">
|
||||
<Input type="date" bind:value={pendingFrom.date} />
|
||||
</FormGroup>
|
||||
<FormGroup class="col">
|
||||
<Input type="time" bind:value={pendingFrom.time} />
|
||||
</FormGroup>
|
||||
</Row>
|
||||
<h4>To</h4>
|
||||
<Row>
|
||||
<FormGroup class="col">
|
||||
<Input type="date" bind:value={pendingTo.date} />
|
||||
</FormGroup>
|
||||
<FormGroup class="col">
|
||||
<Input type="time" bind:value={pendingTo.time} />
|
||||
</FormGroup>
|
||||
</Row>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={pendingFrom.date == "0000-00-00" ||
|
||||
pendingTo.date == "0000-00-00"}
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
from = toRFC3339(pendingFrom);
|
||||
to = toRFC3339(pendingTo, "59");
|
||||
dispatch("set-filter", { from, to });
|
||||
}}
|
||||
>
|
||||
Close & Apply
|
||||
</Button>
|
||||
<Button
|
||||
color="danger"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
from = null;
|
||||
to = null;
|
||||
reset();
|
||||
dispatch("set-filter", { from, to });
|
||||
}}>Reset</Button
|
||||
>
|
||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
95
web/frontend/src/generic/filters/Stats.svelte
Normal file
95
web/frontend/src/generic/filters/Stats.svelte
Normal file
@@ -0,0 +1,95 @@
|
||||
<!--
|
||||
@component Filter sub-component for selecting job statistics
|
||||
|
||||
Properties:
|
||||
- `isModified Bool?`: Is this filter component modified [Default: false]
|
||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||
- `stats [Object]?`: The currently selected statistics filter [Default: []]
|
||||
|
||||
Events:
|
||||
- `set-filter, {[Object]}`: Set 'stats' filter in upstream component
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
import { getStatsItems } from "../utils.js";
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
||||
|
||||
const initialized = getContext("initialized"),
|
||||
dispatch = createEventDispatcher();
|
||||
|
||||
export let isModified = false;
|
||||
export let isOpen = false;
|
||||
export let stats = [];
|
||||
|
||||
let statistics = []
|
||||
function loadRanges(isInitialized) {
|
||||
if (!isInitialized) return;
|
||||
statistics = getStatsItems();
|
||||
}
|
||||
|
||||
function resetRanges() {
|
||||
for (let st of statistics) {
|
||||
st.enabled = false
|
||||
st.from = 0
|
||||
st.to = st.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>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
<ModalHeader>Filter based on statistics (of non-running jobs)</ModalHeader>
|
||||
<ModalBody>
|
||||
{#each statistics as stat}
|
||||
<h4>{stat.text}</h4>
|
||||
<DoubleRangeSlider
|
||||
on:change={({ detail }) => (
|
||||
(stat.from = detail[0]), (stat.to = detail[1]), (stat.enabled = true)
|
||||
)}
|
||||
min={0}
|
||||
max={stat.peak}
|
||||
firstSlider={stat.from}
|
||||
secondSlider={stat.to}
|
||||
inputFieldFrom={stat.from}
|
||||
inputFieldTo={stat.to}
|
||||
/>
|
||||
{/each}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
stats = statistics.filter((stat) => stat.enabled);
|
||||
dispatch("set-filter", { stats });
|
||||
}}>Close & Apply</Button
|
||||
>
|
||||
<Button
|
||||
color="danger"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
resetRanges();
|
||||
stats = [];
|
||||
dispatch("set-filter", { stats });
|
||||
}}>Reset</Button
|
||||
>
|
||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
101
web/frontend/src/generic/filters/Tags.svelte
Normal file
101
web/frontend/src/generic/filters/Tags.svelte
Normal file
@@ -0,0 +1,101 @@
|
||||
<!--
|
||||
@component Filter sub-component for selecting tags
|
||||
|
||||
Properties:
|
||||
- `isModified Bool?`: Is this filter component modified [Default: false]
|
||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||
- `tags [Number]?`: The currently selected tags (as IDs) [Default: []]
|
||||
|
||||
Events:
|
||||
- `set-filter, {[Number]}`: Set 'tag' filter in upstream component
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
import {
|
||||
Button,
|
||||
ListGroup,
|
||||
ListGroupItem,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
Icon,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { fuzzySearchTags } from "../utils.js";
|
||||
import Tag from "../helper/Tag.svelte";
|
||||
|
||||
const allTags = getContext("tags"),
|
||||
initialized = getContext("initialized"),
|
||||
dispatch = createEventDispatcher();
|
||||
|
||||
export let isModified = false;
|
||||
export let isOpen = false;
|
||||
export let tags = [];
|
||||
|
||||
let pendingTags = [...tags];
|
||||
$: isModified =
|
||||
tags.length != pendingTags.length ||
|
||||
!tags.every((tagId) => pendingTags.includes(tagId));
|
||||
|
||||
let searchTerm = "";
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
<ModalHeader>Select Tags</ModalHeader>
|
||||
<ModalBody>
|
||||
<Input type="text" placeholder="Search" bind:value={searchTerm} />
|
||||
<br />
|
||||
<ListGroup>
|
||||
{#if $initialized}
|
||||
{#each fuzzySearchTags(searchTerm, allTags) as tag (tag)}
|
||||
<ListGroupItem>
|
||||
{#if pendingTags.includes(tag.id)}
|
||||
<Button
|
||||
outline
|
||||
color="danger"
|
||||
on:click={() =>
|
||||
(pendingTags = pendingTags.filter((id) => id != tag.id))}
|
||||
>
|
||||
<Icon name="dash-circle" />
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
outline
|
||||
color="success"
|
||||
on:click={() => (pendingTags = [...pendingTags, tag.id])}
|
||||
>
|
||||
<Icon name="plus-circle" />
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
<Tag {tag} />
|
||||
</ListGroupItem>
|
||||
{:else}
|
||||
<ListGroupItem disabled>No Tags</ListGroupItem>
|
||||
{/each}
|
||||
{/if}
|
||||
</ListGroup>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
tags = [...pendingTags];
|
||||
dispatch("set-filter", { tags });
|
||||
}}>Close & Apply</Button
|
||||
>
|
||||
<Button
|
||||
color="danger"
|
||||
on:click={() => {
|
||||
isOpen = false;
|
||||
tags = [];
|
||||
pendingTags = [];
|
||||
dispatch("set-filter", { tags });
|
||||
}}>Reset</Button
|
||||
>
|
||||
<Button on:click={() => (isOpen = false)}>Close</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
Reference in New Issue
Block a user