mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-10-02 20:54:32 +02:00
Migrate select components and adapt parents
This commit is contained in:
@@ -20,16 +20,24 @@
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||
|
||||
export let cluster;
|
||||
export let selectedHistograms;
|
||||
export let isOpen;
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
cluster,
|
||||
isOpen = $bindable(),
|
||||
presetSelectedHistograms,
|
||||
applyChange
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const client = getContextClient();
|
||||
const initialized = getContext("initialized");
|
||||
|
||||
function loadHistoMetrics(isInitialized, thisCluster) {
|
||||
if (!isInitialized) return [];
|
||||
/* Derived */
|
||||
let selectedHistograms = $derived(presetSelectedHistograms); // Non-Const Derived: Is settable
|
||||
const availableMetrics = $derived(loadHistoMetrics(cluster));
|
||||
|
||||
/* Functions */
|
||||
function loadHistoMetrics(thisCluster) {
|
||||
// isInit Check Removed: Parent Component has finished Init-Query: Globalmetrics available here.
|
||||
if (!thisCluster) {
|
||||
return getContext("globalMetrics")
|
||||
.filter((gm) => gm?.footprint)
|
||||
@@ -42,18 +50,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const updateConfigurationMutation = ({ name, value }) => {
|
||||
return mutationStore({
|
||||
client: client,
|
||||
query: gql`
|
||||
mutation ($name: String!, $value: String!) {
|
||||
updateConfiguration(name: $name, value: $value)
|
||||
}
|
||||
`,
|
||||
variables: { name, value },
|
||||
});
|
||||
};
|
||||
|
||||
function updateConfiguration(data) {
|
||||
updateConfigurationMutation({
|
||||
name: data.name,
|
||||
@@ -67,6 +63,7 @@
|
||||
|
||||
function closeAndApply() {
|
||||
isOpen = !isOpen;
|
||||
applyChange(selectedHistograms)
|
||||
updateConfiguration({
|
||||
name: cluster
|
||||
? `user_view_histogramMetrics:${cluster}`
|
||||
@@ -75,8 +72,18 @@
|
||||
});
|
||||
}
|
||||
|
||||
$: availableMetrics = loadHistoMetrics($initialized, cluster);
|
||||
|
||||
/* Mutation */
|
||||
const updateConfigurationMutation = ({ name, value }) => {
|
||||
return mutationStore({
|
||||
client: client,
|
||||
query: gql`
|
||||
mutation ($name: String!, $value: String!) {
|
||||
updateConfiguration(name: $name, value: $value)
|
||||
}
|
||||
`,
|
||||
variables: { name, value },
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import {
|
||||
Icon,
|
||||
Button,
|
||||
@@ -18,44 +18,73 @@
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { getSortItems } from "../utils.js";
|
||||
|
||||
export let isOpen = false;
|
||||
export let sorting = { field: "startTime", type: "col", order: "DESC" };
|
||||
|
||||
let sortableColumns = [];
|
||||
let activeColumnIdx;
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
isOpen = $bindable(false),
|
||||
presetSorting = { field: "startTime", type: "col", order: "DESC" },
|
||||
applySorting
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const initialized = getContext("initialized");
|
||||
|
||||
function loadSortables(isInitialized) {
|
||||
if (!isInitialized) return;
|
||||
sortableColumns = [
|
||||
{ field: "startTime", type: "col", text: "Start Time", order: "DESC" },
|
||||
{ field: "duration", type: "col", text: "Duration", order: "DESC" },
|
||||
{ field: "numNodes", type: "col", text: "Number of Nodes", order: "DESC" },
|
||||
{ field: "numHwthreads", type: "col", text: "Number of HWThreads", order: "DESC" },
|
||||
{ field: "numAcc", type: "col", text: "Number of Accelerators", order: "DESC" },
|
||||
{ field: "energy", type: "col", text: "Total Energy", order: "DESC" },
|
||||
...getSortItems()
|
||||
]
|
||||
}
|
||||
const globalMetrics = getContext("globalMetrics");
|
||||
const fixedSortables = $state([
|
||||
{ field: "startTime", type: "col", text: "Start Time (Default)", order: "DESC" },
|
||||
{ field: "duration", type: "col", text: "Duration", order: "DESC" },
|
||||
{ field: "numNodes", type: "col", text: "Number of Nodes", order: "DESC" },
|
||||
{ field: "numHwthreads", type: "col", text: "Number of HWThreads", order: "DESC" },
|
||||
{ field: "numAcc", type: "col", text: "Number of Accelerators", order: "DESC" },
|
||||
{ field: "energy", type: "col", text: "Total Energy", order: "DESC" },
|
||||
]);
|
||||
|
||||
function loadActiveIndex(isInitialized) {
|
||||
if (!isInitialized) return;
|
||||
/* State Init */
|
||||
let sorting = $state({...presetSorting})
|
||||
let activeColumnIdx = $state(0);
|
||||
let metricSortables = $state([]);
|
||||
|
||||
/* Derived */
|
||||
let sortableColumns = $derived([...fixedSortables, ...metricSortables]);
|
||||
|
||||
/* Effect */
|
||||
$effect(() => {
|
||||
if ($initialized) {
|
||||
loadMetricSortables();
|
||||
};
|
||||
});
|
||||
|
||||
/* Functions */
|
||||
function loadMetricSortables() {
|
||||
metricSortables = globalMetrics.map((gm) => {
|
||||
if (gm?.footprint) {
|
||||
return {
|
||||
field: gm.name + '_' + gm.footprint,
|
||||
type: 'foot',
|
||||
text: gm.name + ' (' + gm.footprint + ')',
|
||||
order: 'DESC'
|
||||
}
|
||||
}
|
||||
return null
|
||||
}).filter((r) => r != null)
|
||||
};
|
||||
|
||||
function loadActiveIndex() {
|
||||
activeColumnIdx = sortableColumns.findIndex(
|
||||
(col) => col.field == sorting.field,
|
||||
);
|
||||
sortableColumns[activeColumnIdx].order = sorting.order;
|
||||
}
|
||||
|
||||
$: loadSortables($initialized);
|
||||
$: loadActiveIndex($initialized)
|
||||
function resetSorting(sort) {
|
||||
sorting = {...sort};
|
||||
loadActiveIndex();
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
{isOpen}
|
||||
toggle={() => {
|
||||
resetSorting(presetSorting);
|
||||
isOpen = !isOpen;
|
||||
}}
|
||||
>
|
||||
@@ -66,7 +95,7 @@
|
||||
<ListGroupItem>
|
||||
<button
|
||||
class="sort"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (activeColumnIdx == i) {
|
||||
col.order = col.order == "DESC" ? "ASC" : "DESC";
|
||||
} else {
|
||||
@@ -96,11 +125,27 @@
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
on:click={() => {
|
||||
color="warning"
|
||||
onclick={() => {
|
||||
isOpen = false;
|
||||
}}>Close</Button
|
||||
resetSorting({ field: "startTime", type: "col", order: "DESC" });
|
||||
applySorting(sorting);
|
||||
}}>Reset</Button
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
onclick={() => {
|
||||
applySorting(sorting);
|
||||
isOpen = false;
|
||||
}}>Close & Apply</Button
|
||||
>
|
||||
<Button
|
||||
color="secondary"
|
||||
onclick={() => {
|
||||
resetSorting(presetSorting);
|
||||
isOpen = false
|
||||
}}>Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
|
@@ -12,7 +12,6 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import {
|
||||
Icon,
|
||||
Input,
|
||||
@@ -20,78 +19,96 @@
|
||||
InputGroupText,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
export let from;
|
||||
export let to;
|
||||
export let customEnabled = true;
|
||||
export let options = {
|
||||
"Last quarter hour": 15 * 60,
|
||||
"Last half hour": 30 * 60,
|
||||
"Last hour": 60 * 60,
|
||||
"Last 2hrs": 2 * 60 * 60,
|
||||
"Last 4hrs": 4 * 60 * 60,
|
||||
"Last 12hrs": 12 * 60 * 60,
|
||||
"Last 24hrs": 24 * 60 * 60,
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
presetFrom,
|
||||
presetTo,
|
||||
customEnabled = true,
|
||||
options = {
|
||||
"Last quarter hour": 15 * 60,
|
||||
"Last half hour": 30 * 60,
|
||||
"Last hour": 60 * 60,
|
||||
"Last 2hrs": 2 * 60 * 60,
|
||||
"Last 4hrs": 4 * 60 * 60,
|
||||
"Last 12hrs": 12 * 60 * 60,
|
||||
"Last 24hrs": 24 * 60 * 60,
|
||||
},
|
||||
applyTime
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const defaultTo = new Date(Date.now());
|
||||
const defaultFrom = new Date(defaultTo.setHours(defaultTo.getHours() - 4));
|
||||
|
||||
/* State Init */
|
||||
let timeType = $state("range");
|
||||
let pendingCustomFrom = $state(null);
|
||||
let pendingCustomTo = $state(null);
|
||||
|
||||
/* Derived */
|
||||
let timeRange = $derived.by(() => {
|
||||
if (presetTo && presetFrom) {
|
||||
return ((presetTo.getTime() - presetFrom.getTime()) / 1000)
|
||||
} else {
|
||||
return ((defaultTo.getTime() - defaultFrom.getTime()) / 1000)
|
||||
}
|
||||
});
|
||||
let unknownRange = $derived(!Object.values(options).includes(timeRange));
|
||||
|
||||
/* Functions */
|
||||
function updateTimeRange() {
|
||||
let now = Date.now();
|
||||
let t = timeRange * 1000;
|
||||
applyTime(new Date(now - t), new Date(now));
|
||||
};
|
||||
|
||||
$: pendingFrom = from;
|
||||
$: pendingTo = to;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let timeRange = // If both times set, return diff, else: display custom select
|
||||
(to && from) ? ((to.getTime() - from.getTime()) / 1000) : -1;
|
||||
|
||||
function updateTimeRange() {
|
||||
if (timeRange == -1) {
|
||||
pendingFrom = null;
|
||||
pendingTo = null;
|
||||
return;
|
||||
function updateTimeCustom() {
|
||||
if (pendingCustomFrom && pendingCustomTo) {
|
||||
applyTime(new Date(pendingCustomFrom), new Date(pendingCustomTo));
|
||||
}
|
||||
|
||||
let now = Date.now(),
|
||||
t = timeRange * 1000;
|
||||
from = pendingFrom = new Date(now - t);
|
||||
to = pendingTo = new Date(now);
|
||||
dispatch("change", { from, to });
|
||||
}
|
||||
|
||||
function updateExplicitTimeRange(type, event) {
|
||||
let d = new Date(Date.parse(event.target.value));
|
||||
if (type == "from") pendingFrom = d;
|
||||
else pendingTo = d;
|
||||
|
||||
if (pendingFrom != null && pendingTo != null) {
|
||||
from = pendingFrom;
|
||||
to = pendingTo;
|
||||
dispatch("change", { from, to });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<InputGroup class="inline-from">
|
||||
<InputGroupText><Icon name="clock-history" /></InputGroupText>
|
||||
<InputGroupText>Range</InputGroupText>
|
||||
<select
|
||||
class="form-select"
|
||||
bind:value={timeRange}
|
||||
on:change={updateTimeRange}
|
||||
>
|
||||
{#if customEnabled}
|
||||
<option value={-1}>Custom</option>
|
||||
{/if}
|
||||
{#each Object.entries(options) as [name, seconds]}
|
||||
<option value={seconds}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if timeRange == -1}
|
||||
{#if customEnabled}
|
||||
<Input
|
||||
type="select"
|
||||
style="max-width:fit-content;background-color:#f8f9fa;"
|
||||
bind:value={timeType}
|
||||
>
|
||||
<option value="range">Range</option>
|
||||
<option value="custom">Custom</option>
|
||||
</Input>
|
||||
{:else}
|
||||
<InputGroupText>Range</InputGroupText>
|
||||
{/if}
|
||||
|
||||
{#if timeType === "range"}
|
||||
<Input
|
||||
type="select"
|
||||
bind:value={timeRange}
|
||||
onchange={updateTimeRange}
|
||||
>
|
||||
{#if unknownRange}
|
||||
<option value={timeRange} disabled>Select new range...</option>
|
||||
{/if}
|
||||
{#each Object.entries(options) as [name, seconds]}
|
||||
<option value={seconds}>{name}</option>
|
||||
{/each}
|
||||
</Input>
|
||||
{:else}
|
||||
<InputGroupText>from</InputGroupText>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
on:change={(event) => updateExplicitTimeRange("from", event)}
|
||||
bind:value={pendingCustomFrom}
|
||||
onchange={updateTimeCustom}
|
||||
></Input>
|
||||
<InputGroupText>to</InputGroupText>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
on:change={(event) => updateExplicitTimeRange("to", event)}
|
||||
bind:value={pendingCustomTo}
|
||||
onchange={updateTimeCustom}
|
||||
></Input>
|
||||
{/if}
|
||||
</InputGroup>
|
||||
|
Reference in New Issue
Block a user