Update component header, format, streamline SV5 components

This commit is contained in:
Christoph Kluge
2025-07-02 18:43:25 +02:00
parent dd48f5ab87
commit 60ec7e54f5
76 changed files with 2070 additions and 1988 deletions

View File

@@ -1,19 +1,20 @@
<!--
@component Main filter component; handles filter object on sub-component changes before dispatching it
@component Main filter component; handles filter object on sub-component changes before dispatching it
Properties:
- `menuText String?`: Optional text to show in the dropdown menu [Default: null]
- `filterPresets Object?`: Optional predefined filter values [Default: {}]
- `disableClusterSelection Bool?`: Is the selection disabled [Default: false]
- `startTimeQuickSelect Bool?`: Render startTime quick selections [Default: false]
- `matchedJobs Number?`: Number of jobs matching the filter [Default: -2]
Events:
- `update-filters, {filters: [Object]?}`: The detail's 'filters' prop are new filter items to be applied
Functions:
- `void updateFilters (additionalFilters: Object?)`: Handles new filters from nested components, triggers upstream update event
-->
Properties:
- `menuText String?`: Optional text to show in the dropdown menu [Default: null]
- `filterPresets Object?`: Optional predefined filter values [Default: {}]
- `disableClusterSelection Bool?`: Is the selection disabled [Default: false]
- `startTimeQuickSelect Bool?`: Render startTime quick selections [Default: false]
- `matchedJobs Number?`: Number of jobs matching the filter [Default: -2]
- `showFilter Func`: If the filter component should be rendered in addition to total count info [Default: true]
- `applyFilters Func`: The callback function to apply current filter selection
Functions:
- `void updateFilters (additionalFilters: Object, force: Bool)`:
Handles new filters from nested components, triggers upstream update event.
'additionalFilters' usually added to existing selection, but can be forced to overwrite instead.
-->
<script>
import {
@@ -510,8 +511,6 @@
setFilter={(filter) => updateFilters(filter)}
/>
<style>
:global(.cc-dropdown-on-hover:hover .dropdown-menu) {
display: block;

View File

@@ -1,13 +1,13 @@
<!--
@component jobCompare component; compares jobs according to set filters or job selection
@component jobCompare component; compares jobs according to set filters or job selection
Properties:
- `matchedCompareJobs Number?`: Number of matched jobs for selected filters [Default: 0]
- `metrics [String]?`: The currently selected metrics [Default: User-Configured Selection]
- `showFootprint Bool`: If to display the jobFootprint component
Properties:
- `matchedCompareJobs Number?`: Number of matched jobs for selected filters [Bindable, Default: 0]
- `metrics [String]?`: The currently selected metrics [Default: User-Configured Selection]
- `filterBuffer [Object]?`: Latest selected filters to keep for view switch to job list [Default: []]
Functions:
- `queryJobs(filters?: [JobFilter])`: Load jobs data with new filters, starts from page 1
Functions:
- `queryJobs(filters?: [JobFilter])`: Load jobs data with new filters, starts from page 1
-->
<script>
@@ -104,6 +104,7 @@
/* Effect */
$effect(() => {
// Update bound property
matchedCompareJobs = $compareData?.data != null ? $compareData.data.jobsMetricStats.length : -1;
});

View File

@@ -1,17 +1,19 @@
<!--
@component Main jobList component; lists jobs according to set filters
@component Main jobList component; lists jobs according to set filters
Properties:
- `sorting Object?`: Currently active sorting [Default: {field: "startTime", type: "col", order: "DESC"}]
- `matchedListJobs Number?`: Number of matched jobs for selected filters [Default: 0]
- `metrics [String]?`: The currently selected metrics [Default: User-Configured Selection]
- `showFootprint Bool`: If to display the jobFootprint component
Properties:
- `sorting Object?`: Currently active sorting [Default: {field: "startTime", type: "col", order: "DESC"}]
- `matchedListJobs Number?`: Number of matched jobs for selected filters [Bindable, Default: 0]
- `metrics [String]?`: The currently selected metrics [Default: User-Configured Selection]
- `showFootprint Bool?`: If to display the jobFootprint component [Default: false]
- `selectedJobs [Number]?`: IDs of jobs selected for job comparison [Bindable, Default: []]
- `filterBuffer [Object]?`: Latest selected filters to keep for view switch to job compare [Default: []]
Functions:
- `refreshJobs()`: Load jobs data with unchanged parameters and 'network-only' keyword
- `refreshAllMetrics()`: Trigger downstream refresh of all running jobs' metric data
- `queryJobs(filters?: [JobFilter])`: Load jobs data with new filters, starts from page 1
-->
Functions:
- `refreshJobs()`: Load jobs data with unchanged parameters and 'network-only' keyword
- `refreshAllMetrics()`: Trigger downstream refresh of all running jobs' metric data
- `queryJobs(filters?: [JobFilter])`: Load jobs data with new filters, starts from page 1
-->
<script>
import { getContext, untrack } from "svelte";

View File

@@ -1,9 +1,10 @@
<!--
@component Organized display of plots as bootstrap (sveltestrap) grid
@component Organized display of svelte 5 snippets as bootstrap (sveltestrap) grid
Properties:
- `itemsPerRow Number`: Elements to render per row
- `items [Any]`: List of plot components to render
Properties:
- `items [Any]`: Array of information required for gridContent SV5 snippet
- `itemsPerRow Number`: Elements to render per row
- `gridContent Func`: Svelte 5 Snippet from Upstream; Defines what and how to render $item data
-->
<script>

View File

@@ -1,16 +1,13 @@
<!--
@component Filter sub-component for selecting cluster and subCluster
@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
-->
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetCluster String?`: The latest selected cluster [Default: ""]
- `presetPartition String?`: The latest selected partition [Default: ""]
- `disableClusterSelection Bool?`: Is the selection disabled [Default: false]
- `setFilter Func`: The callback function to apply current filter selection
-->
<script>
import { getContext } from "svelte";
@@ -33,14 +30,13 @@
setFilter
} = $props();
/* Const Init */
const clusters = getContext("clusters");
const initialized = getContext("initialized");
/* State Init */
let pendingCluster = $state(presetCluster);
let pendingPartition = $state(presetPartition);
/* Derived Vars */
const clusters = $derived(getContext("clusters"));
const initialized = $derived(getContext("initialized"));
</script>
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>

View File

@@ -1,16 +1,12 @@
<!--
@component Filter sub-component for selecting job duration
@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
-->
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetDuration Object?`: Object containing the latest duration filter parameters
- Default: { lessThan: null, moreThan: null, from: null, to: null }
- `setFilter Func`: The callback function to apply current filter selection
-->
<script>
import {
@@ -26,7 +22,12 @@
/* Svelte 5 Props */
let {
isOpen = $bindable(false),
presetDuration ={lessThan: null, moreThan: null, from: null, to: null},
presetDuration = {
lessThan: null,
moreThan: null,
from: null,
to: null
},
setFilter
} = $props();

View File

@@ -1,13 +1,12 @@
<!--
@component Filter sub-component for selecting job energies
@component Filter sub-component for selecting job energies
Properties:
- `isOpen Bool?`: Is this filter component opened [Default: false]
- `energy Object?`: The currently selected total energy filter [Default: {from:null, to:null}]
Events:
- `set-filter, {Object}`: Set 'energy' filter in upstream component
-->
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, efault: false]
- `presetEnergy Object?`: Object containing the latest energy filter parameters
- Default: { from: null, to: null }
- `setFilter Func`: The callback function to apply current filter selection
-->
<script>
import {
@@ -22,13 +21,15 @@
/* Svelte 5 Props */
let {
isOpen = $bindable(false),
presetEnergy= {from: null, to: null},
presetEnergy = {
from: null,
to: null
},
setFilter,
} = $props();
/* State Init */
let energyState = $state(presetEnergy);
</script>
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>

View File

@@ -1,17 +1,23 @@
<!--
@component Info pill displayed for active filters
@component Info pill displayed for active filters
Properties:
- `icon String`: Sveltestrap icon name
- `modified Bool?`: Optional if filter is modified [Default: false]
- `onclick Fn()`: Opens Modal on click
- `children Fn()?`: Internal prop, Svelte 5 version of <slot/>
-->
Properties:
- `icon String`: Sveltestrap icon name
- `modified Bool?`: Optional if filter is modified
- `onclick Func`: Opens Modal on click
- `children Func`: Internal prop, Svelte 5 version of <slot/>
-->
<script>
import { Button, Icon } from "@sveltestrap/sveltestrap";
/* Svelte 5 Props */
let { icon, modified, onclick, children } = $props();
let {
icon,
modified,
onclick,
children
} = $props();
</script>
<Button class="mr-2 mb-1" outline color={modified ? "warning" : "primary"} {onclick}>

View File

@@ -1,17 +1,14 @@
<!--
@component Filter sub-component for selecting job states
@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]]
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetStates [String]?`: The latest selected filter state [Default: [...allJobStates]]
- `setFilter Func`: The callback function to apply current filter selection
Events:
- `set-filter, {[String]}`: Set 'states' filter in upstream component
Exported:
- `const allJobStates [String]`: List of all available job states used in cc-backend
-->
Exported:
- `const allJobStates [String]`: List of all available job states used in cc-backend
-->
<script module>
export const allJobStates = [

View File

@@ -1,17 +1,16 @@
<!--
@component Filter sub-component for selecting job resources
@component Filter sub-component for selecting job resources
Properties:
- `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}]
- `numHWThreads Object?`: The currently selected numHWThreads filter [Default: {from:null, to:null}]
- `numAccelerators Object?`: The currently selected numAccelerators filter [Default: {from:null, to:null}]
- `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
-->
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `activeCluster String?`: The currently selected cluster name [Default: null]
- `presetNumNodes Object?`: The currently selected numNodes filter [Default: {from:null, to:null}]
- `presetNumHWThreads Object?`: The currently selected numHWThreads filter [Default: {from:null, to:null}]
- `presetNumAccelerators Object?`: The currently selected numAccelerators filter [Default: {from:null, to:null}]
- `presetNamedNode String?`: The currently selected single named node (= hostname) [Default: null]
- `presetNodeMatch String?`: The currently selected single named node (= hostname) [Default: "eq"]
- `setFilter Func`: The callback function to apply current filter selection
-->
<script>
import { getContext } from "svelte";

View File

@@ -1,16 +1,15 @@
<!--
@component Filter sub-component for selecting job starttime
@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]
- `range String?`: The currently selected starttime range as string [Default: ""]
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetStartTime Object?`: Object containing the latest duration filter parameters
- Default: { from: null, to: null, range: "" }
- `setFilter Func`: The callback function to apply current filter selection
Events:
- `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component
-->
Exported:
- `const startTimeSelectOptions [Object]`: List of available fixed startTimes used in cc-backend
-->
<script module>
export const startTimeSelectOptions = [
@@ -24,7 +23,6 @@
<script>
/* Note: Ignore VSCode reported 'A component can only have one instance-level <script> element' error */
import { parse, format, sub } from "date-fns";
import {
Row,

View File

@@ -1,13 +1,11 @@
<!--
@component Filter sub-component for selecting job statistics
@component Filter sub-component for selecting job statistics
Properties:
- `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
-->
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetStats [Object]?`: The latest selected statistics filter
- `setFilter Func`: The callback function to apply current filter selection
-->
<script>
import { getStatsItems } from "../utils.js";

View File

@@ -1,14 +1,11 @@
<!--
@component Filter sub-component for selecting tags
@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
-->
Properties:
- `isOpen Bool?`: Is this filter component opened [Bindable, Default: false]
- `presetTags [Number]?`: The currently selected tags (as IDs) [Default: []]
- `setFilter Func`: The callback function to apply current filter selection
-->
<script>
import { getContext } from "svelte";

View File

@@ -1,13 +1,13 @@
<!--
@component Concurrent Jobs Component; Lists all concurrent jobs in one scrollable card.
@component Concurrent Jobs Component; Lists all concurrent jobs in one scrollable card.
Properties:
- `cJobs JobLinkResultList`: List of concurrent Jobs
- `showLinks Bool?`: Show list as clickable links [Default: false]
- `renderCard Bool?`: If to render component as content only or with card wrapping [Default: true]
- `width String?`: Width of the card [Default: 'auto']
- `height String?`: Height of the card [Default: '310px']
-->
Properties:
- `cJobs JobLinkResultList`: List of concurrent Jobs
- `showLinks Bool?`: Show list as clickable links [Default: false]
- `renderCard Bool?`: If to render component as content only or with card wrapping [Default: false]
- `width String?`: Width of the card [Default: 'auto']
- `height String?`: Height of the card [Default: '400px']
-->
<script>
import {
@@ -30,12 +30,12 @@
{#if renderCard}
<Card class="overflow-auto" style="width: {width}; height: {height}">
<CardHeader class="mb-0 d-flex justify-content-center">
{cJobs.items.length} Concurrent Jobs
<Icon
style="cursor:help; margin-left:0.5rem;"
name="info-circle"
title="Jobs running on the same node with overlapping runtimes using shared resources"
/>
{cJobs.items.length} Concurrent Jobs
<Icon
style="cursor:help; margin-left:0.5rem;"
name="info-circle"
title="Jobs running on the same node with overlapping runtimes using shared resources"
/>
</CardHeader>
<CardBody>
{#if showLinks}

View File

@@ -1,12 +1,12 @@
<!--
@component Footprint component; Displays job.footprint data as bars in relation to thresholds
@component Footprint component; Displays job.footprint data as bars in relation to thresholds
Properties:
- `job Object`: The GQL job object
- `displayTitle Bool?`: If to display cardHeader with title [Default: true]
- `width String?`: Width of the card [Default: 'auto']
- `height String?`: Height of the card [Default: '310px']
-->
Properties:
- `job Object`: The GQL job object
- `displayTitle Bool?`: If to display cardHeader with title [Default: true]
- `width String?`: Width of the card [Default: 'auto']
- `height String?`: Height of the card [Default: '310px']
-->
<script>
import { getContext } from "svelte";

View File

@@ -1,12 +1,12 @@
<!--
@component Triggers upstream data refresh in selectable intervals
@component Triggers upstream data refresh in selectable intervals
Properties:
- `initially Number?`: Initial refresh interval on component mount, in seconds [Default: null]
Properties:
- `initially Number?`: Initial refresh interval on component mount, in seconds [Default: null]
- `presetClass String?`: Custom class to apply to main <InputGroup>
- `onRefresh Func`: The callback function to perform at refresh times
-->
Events:
- `refresh`: When fired, the upstream component refreshes its contents
-->
<script>
import { Button, Icon, Input, InputGroup } from "@sveltestrap/sveltestrap";

View File

@@ -1,63 +1,63 @@
<!--
@component Single tag pill component
@component Single tag pill component
Properties:
- id: ID! (if the tag-id is known but not the tag type/name, this can be used)
- tag: { id: ID!, type: String, name: String }
- clickable: Boolean (default is true)
-->
Properties:
- `id ID!`: (if the tag-id is known but not the tag type/name, this can be used)
- `tag Object`: The tag Object
- `clickable Bool`: If tag should be click reactive [Default: true]
-->
<script>
import { getContext } from 'svelte'
/* Svelte 5 Props */
let {
id = null,
tag = null,
clickable = true
} = $props();
import { getContext } from 'svelte'
/* Svelte 5 Props */
let {
id = null,
tag = null,
clickable = true
} = $props();
/* Derived */
const allTags = $derived(getContext('tags'));
const initialized = $derived(getContext('initialized'));
/* Derived */
const allTags = $derived(getContext('tags'));
const initialized = $derived(getContext('initialized'));
/* Effects */
$effect(() => {
if (tag != null && id == null)
id = tag.id
});
/* Effects */
$effect(() => {
if (tag != null && id == null)
id = tag.id
});
$effect(() => {
if ($initialized && tag == null)
tag = allTags.find(tag => tag.id == id)
});
$effect(() => {
if ($initialized && tag == null)
tag = allTags.find(tag => tag.id == id)
});
/* Function*/
function getScopeColor(scope) {
switch (scope) {
case "admin":
return "#19e5e6";
case "global":
return "#c85fc8";
default:
return "#ffc107";
}
/* Function*/
function getScopeColor(scope) {
switch (scope) {
case "admin":
return "#19e5e6";
case "global":
return "#c85fc8";
default:
return "#ffc107";
}
}
</script>
<style>
a {
margin-right: 0.5rem;
}
span {
font-size: 0.9rem;
}
</style>
<a target={clickable ? "_blank" : null} href={clickable ? `/monitoring/jobs/?tag=${id}` : null}>
{#if tag}
<span style="background-color:{getScopeColor(tag?.scope)};" class="my-1 badge text-dark">{tag.type}: {tag.name}</span>
{:else}
Loading...
{/if}
{#if tag}
<span style="background-color:{getScopeColor(tag?.scope)};" class="my-1 badge text-dark">{tag.type}: {tag.name}</span>
{:else}
Loading...
{/if}
</a>
<style>
a {
margin-right: 0.5rem;
}
span {
font-size: 0.9rem;
}
</style>

View File

@@ -1,14 +1,15 @@
<!--
@component Job Info Subcomponent; allows management of job tags by deletion or new entries
@component Job Info Subcomponent; allows management of job tags by deletion or new entries
Properties:
- `jobTags [Number]`: The array of currently designated tags [Bindable]
- `job Object`: The job object
- `username String`: Empty string if auth. is disabled, otherwise the username as string
- `authlevel Number`: The current users authentication level
- `roles [Number]`: Enum containing available roles
- `renderModal Bool?`: If component is rendered as bootstrap modal button [Default: true]
-->
Properties:
- `job Object`: The job object
- `jobTags [Number]`: The array of currently designated tags
- `username String`: Empty string if auth. is disabled, otherwise the username as string
- `authlevel Number`: The current users authentication level
- `roles [Number]`: Enum containing available roles
- `renderModal Bool?`: If component is rendered as bootstrap modal button [Default: true]
-->
<script>
import { getContext } from "svelte";
import { gql, getContextClient, mutationStore } from "@urql/svelte";

View File

@@ -1,14 +1,12 @@
<!--
@component Search Field for Job-Lists with separate mode if project filter is active
@component Search Field for Job-Lists with separate mode if project filter is active
Properties:
- `presetProject String?`: Currently active project filter [Default: '']
- `authlevel Number?`: The current users authentication level [Default: null]
- `roles [Number]?`: Enum containing available roles [Default: null]
Events:
- `set-filter, {String?, String?, String?}`: Set 'user, project, jobName' filter in upstream component
-->
Properties:
- `presetProject String?`: Currently active project filter [Default: '']
- `authlevel Number?`: The current users authentication level [Default: null]
- `roles [Number]?`: Enum containing available roles [Default: null]
- `setFilter Func`: The callback function to apply current filter selection
-->
<script>
import { InputGroup, Input, Button, Icon } from "@sveltestrap/sveltestrap";

View File

@@ -1,10 +1,16 @@
<!--
@component Displays job metaData, serves links to detail pages
@component Displays job metaData, serves links to detail pages
Properties:
- `job Object`: The Job Object (GraphQL.Job)
- `jobTags [Number]?`: The jobs tags as IDs, default useful for dynamically updating the tags [Default: job.tags]
-->
Properties:
- `job Object`: The Job Object (GraphQL.Job)
- `jobTags [Number]?`: The jobs tags as IDs, default useful for dynamically updating the tags [Default: job.tags]
- `showJobSelect Bool?`: Show job selection interface for job comparison [Default: false]
- `showTagEdit Bool?`: Show tag editing interface [Default: false]
- `username String?`: The current username
- `authlevel Number`: The current user authentication level
- `roles [String]`: Available roles
- `isSelected Bool`: Whether job is selected for comparison [Bindable, Default: false]
-->
<script>
import { Badge, Button, Icon, Tooltip } from "@sveltestrap/sveltestrap";
@@ -16,12 +22,12 @@
let {
job,
jobTags = job.tags,
showTagedit = false,
showJobSelect = false,
showTagEdit = false,
username = null,
authlevel= null,
authlevel = null,
roles = null,
isSelected = $bindable(),
showSelect = false,
isSelected = $bindable(false),
} = $props();
/* State Init */
@@ -49,7 +55,6 @@
}
function clipJobId(jid) {
// Navigator clipboard api needs a secure context (https)
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard
@@ -82,7 +87,7 @@
({job.cluster})
</span>
<span>
{#if showSelect}
{#if showJobSelect}
<Button id={`${job.cluster}-${job.jobId}-select`} outline={!isSelected} color={isSelected? `success`: `secondary`} size="sm" class="mr-2"
onclick={() => {
isSelected = !isSelected
@@ -193,7 +198,7 @@
{/if}
</p>
{#if showTagedit}
{#if showTagEdit}
<hr class="mt-0 mb-2"/>
<p class="mb-1">
<TagManagement bind:jobTags {job} {username} {authlevel} {roles} renderModal/> :

View File

@@ -1,14 +1,17 @@
<!--
@component Data row for a single job displaying metric plots
@component Data row for a single job displaying metric plots
Properties:
- `job Object`: The job object (GraphQL.Job)
- `metrics [String]`: Currently selected metrics
- `plotWidth Number`: Width of the sub-components
- `plotHeight Number?`: Height of the sub-components [Default: 275]
- `showFootprint Bool`: Display of footprint component for job
- `triggerMetricRefresh Bool?`: If changed to true from upstream, will trigger metric query
-->
Properties:
- `job Object`: The job object (GraphQL.Job)
- `metrics [String]`: Currently selected metrics
- `plotWidth Number`: Width of the sub-components
- `plotHeight Number?`: Height of the sub-components [Default: 275]
- `showFootprint Bool`: Display of footprint component for job [Default: false]
- `previousSelect Bool`: The latest job select state for job comparison [Default: false]
- `triggerMetricRefresh Bool?`: If changed to true from upstream, will trigger metric query [Default: false]
- `selectJob Func`: The callback function to select a job for comparison
- `unselectJob Func`: The callback function to unselect a job from comparison
-->
<script>
import { queryStore, gql, getContextClient } from "@urql/svelte";
@@ -21,15 +24,15 @@
/* Svelte 5 Props */
let {
triggerMetricRefresh = false,
job,
metrics,
plotWidth,
plotHeight = 275,
showFootprint,
showFootprint = false,
previousSelect = false,
triggerMetricRefresh = false,
selectJob,
unselectJob
unselectJob,
} = $props();
/* Const Init */

View File

@@ -1,240 +1,239 @@
<!--
@component Pagination selection component
@component Pagination selection component
Properties:
- page: Number (changes from inside)
- itemsPerPage: Number (changes from inside)
- totalItems: Number (only displayed)
Events:
- "update-paging": { page: Number, itemsPerPage: Number }
- Dispatched once immediately and then each time page or itemsPerPage changes
-->
Properties:
- `page Number?`: Current page [Default: 1]
- `itemsPerPage Number?`: Current items displayed per page [Default: 10]
- `totalItems Number?`: Total count of items [Default: 0]
- `itemText String?`: Name of paged items, e.g. "Jobs" [Default: "items"]
- `pageSizes [Number!]?`: Options available for page sizes [Default: [10, 25, 50]]
- `updatePaging Func`: The callback function to apply current paging selection
-->
<script>
/* Svelte 5 Props */
let {
page = 1,
itemsPerPage = 10,
totalItems = 0,
itemText = "items",
pageSizes = [10,25,50],
updatePaging
} = $props();
/* Svelte 5 Props */
let {
page = 1,
itemsPerPage = 10,
totalItems = 0,
itemText = "items",
pageSizes = [10, 25, 50],
updatePaging
} = $props();
/* Derived */
const backButtonDisabled = $derived((page === 1));
const nextButtonDisabled = $derived((page >= (totalItems / itemsPerPage)));
/* Derived */
const backButtonDisabled = $derived((page === 1));
const nextButtonDisabled = $derived((page >= (totalItems / itemsPerPage)));
/* Functions */
function pageUp ( event ) {
event.preventDefault();
page += 1;
updatePaging({ itemsPerPage, page });
}
function pageBack ( event ) {
event.preventDefault();
page -= 1;
updatePaging({ itemsPerPage, page });
}
/* Functions */
function pageUp ( event ) {
event.preventDefault();
page += 1;
updatePaging({ itemsPerPage, page });
}
function pageBack ( event ) {
event.preventDefault();
page -= 1;
updatePaging({ itemsPerPage, page });
}
function pageReset ( event ) {
event.preventDefault();
page = 1;
updatePaging({ itemsPerPage, page });
}
function pageReset ( event ) {
event.preventDefault();
page = 1;
updatePaging({ itemsPerPage, page });
}
function updateItems ( event ) {
event.preventDefault();
updatePaging({ itemsPerPage, page });
}
function updateItems ( event ) {
event.preventDefault();
updatePaging({ itemsPerPage, page });
}
</script>
<div class="cc-pagination" >
<div class="cc-pagination-left">
<label for="cc-pagination-select">{ itemText } per page:</label>
<div class="cc-pagination-select-wrapper">
<select onblur={(e) => pageReset(e)} onchange={(e) => updateItems(e)} bind:value={itemsPerPage} id="cc-pagination-select" class="cc-pagination-select">
{#each pageSizes as size}
<option value="{size}">{size}</option>
{/each}
</select>
<span class="focus"></span>
</div>
<span class="cc-pagination-text">
{ ((page - 1) * itemsPerPage) + 1 } - { Math.min((page - 1) * itemsPerPage + itemsPerPage, totalItems) } of { totalItems } { itemText }
</span>
</div>
<div class="cc-pagination-right">
{#if !backButtonDisabled}
<button aria-label="page-reset" class="reset nav" type="button"
onclick={(e) => pageReset(e)}></button>
<button aria-label="page-back" class="left nav" type="button"
onclick={(e) => pageBack(e)}></button>
{/if}
{#if !nextButtonDisabled}
<button aria-label="page-up" class="right nav" type="button"
onclick={(e) => pageUp(e)}></button>
{/if}
<div class="cc-pagination-left">
<label for="cc-pagination-select">{ itemText } per page:</label>
<div class="cc-pagination-select-wrapper">
<select onblur={(e) => pageReset(e)} onchange={(e) => updateItems(e)} bind:value={itemsPerPage} id="cc-pagination-select" class="cc-pagination-select">
{#each pageSizes as size}
<option value="{size}">{size}</option>
{/each}
</select>
<span class="focus"></span>
</div>
<span class="cc-pagination-text">
{ ((page - 1) * itemsPerPage) + 1 } - { Math.min((page - 1) * itemsPerPage + itemsPerPage, totalItems) } of { totalItems } { itemText }
</span>
</div>
<div class="cc-pagination-right">
{#if !backButtonDisabled}
<button aria-label="page-reset" class="reset nav" type="button"
onclick={(e) => pageReset(e)}></button>
<button aria-label="page-back" class="left nav" type="button"
onclick={(e) => pageBack(e)}></button>
{/if}
{#if !nextButtonDisabled}
<button aria-label="page-up" class="right nav" type="button"
onclick={(e) => pageUp(e)}></button>
{/if}
</div>
</div>
<style>
*, *::before, *::after {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: border-box;
}
div {
display: flex;
align-items: center;
vertical-align: baseline;
box-sizing: border-box;
}
div {
display: flex;
align-items: center;
vertical-align: baseline;
box-sizing: border-box;
}
label, select, button {
margin: 0;
padding: 0;
vertical-align: baseline;
color: #525252;
}
label, select, button {
margin: 0;
padding: 0;
vertical-align: baseline;
color: #525252;
}
button {
position: relative;
border: none;
border-left: 1px solid #e0e0e0;
height: 3rem;
width: 3rem;
background: 0 0;
transition: all 70ms;
}
button {
position: relative;
border: none;
border-left: 1px solid #e0e0e0;
height: 3rem;
width: 3rem;
background: 0 0;
transition: all 70ms;
}
button:hover {
background-color: #dde1e6;
}
button:hover {
background-color: #dde1e6;
}
button:focus {
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border: 1px solid blue;
border-radius: inherit;
}
button:focus {
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border: 1px solid blue;
border-radius: inherit;
}
.nav::after {
content: "";
width: 0.9em;
height: 0.8em;
background-color: #777;
z-index: 1;
position: absolute;
top: 50%;
left: 50%;
}
.nav::after {
content: "";
width: 0.9em;
height: 0.8em;
background-color: #777;
z-index: 1;
position: absolute;
top: 50%;
left: 50%;
}
.nav:disabled {
background-color: #fff;
cursor: no-drop;
}
.nav:disabled {
background-color: #fff;
cursor: no-drop;
}
.reset::after {
clip-path: polygon(100% 0%, 75% 50%, 100% 100%, 25% 100%, 0% 50%, 25% 0%);
margin-top: -0.3em;
margin-left: -0.5em;
}
.reset::after {
clip-path: polygon(100% 0%, 75% 50%, 100% 100%, 25% 100%, 0% 50%, 25% 0%);
margin-top: -0.3em;
margin-left: -0.5em;
}
.right::after {
clip-path: polygon(100% 50%, 50% 0, 50% 100%);
margin-top: -0.3em;
margin-left: -0.5em;
}
.right::after {
clip-path: polygon(100% 50%, 50% 0, 50% 100%);
margin-top: -0.3em;
margin-left: -0.5em;
}
.left::after {
clip-path: polygon(50% 0, 0 50%, 50% 100%);
margin-top: -0.3em;
margin-left: -0.3em;
}
.left::after {
clip-path: polygon(50% 0, 0 50%, 50% 100%);
margin-top: -0.3em;
margin-left: -0.3em;
}
.cc-pagination-select-wrapper::after {
content: "";
width: 0.8em;
height: 0.5em;
background-color: #777;
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
justify-self: end;
}
.cc-pagination-select-wrapper::after {
content: "";
width: 0.8em;
height: 0.5em;
background-color: #777;
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
justify-self: end;
}
.cc-pagination {
width: 100%;
justify-content: space-between;
border-top: 1px solid #e0e0e0;
}
.cc-pagination {
width: 100%;
justify-content: space-between;
border-top: 1px solid #e0e0e0;
}
.cc-pagination-text {
color: #525252;
margin-left: 1rem;
}
.cc-pagination-text {
color: #525252;
margin-left: 1rem;
}
.cc-pagination-text {
color: #525252;
margin-right: 1rem;
}
.cc-pagination-text {
color: #525252;
margin-right: 1rem;
}
.cc-pagination-left {
padding: 0 1rem;
height: 3rem;
}
.cc-pagination-left {
padding: 0 1rem;
height: 3rem;
}
.cc-pagination-select-wrapper {
display: grid;
grid-template-areas: "select";
align-items: center;
position: relative;
padding: 0 0.5em;
min-width: 3em;
max-width: 6em;
border-right: 1px solid #e0e0e0;
cursor: pointer;
transition: all 70ms;
}
.cc-pagination-select-wrapper {
display: grid;
grid-template-areas: "select";
align-items: center;
position: relative;
padding: 0 0.5em;
min-width: 3em;
max-width: 6em;
border-right: 1px solid #e0e0e0;
cursor: pointer;
transition: all 70ms;
}
.cc-pagination-select-wrapper:hover {
background-color: #dde1e6;
}
.cc-pagination-select-wrapper:hover {
background-color: #dde1e6;
}
select,
.cc-pagination-select-wrapper::after {
grid-area: select;
}
select,
.cc-pagination-select-wrapper::after {
grid-area: select;
}
.cc-pagination-select {
height: 3rem;
appearance: none;
background-color: transparent;
padding: 0 1em 0 0;
margin: 0;
border: none;
width: 100%;
font-family: inherit;
font-size: inherit;
cursor: inherit;
line-height: inherit;
z-index: 1;
outline: none;
}
.cc-pagination-select {
height: 3rem;
appearance: none;
background-color: transparent;
padding: 0 1em 0 0;
margin: 0;
border: none;
width: 100%;
font-family: inherit;
font-size: inherit;
cursor: inherit;
line-height: inherit;
z-index: 1;
outline: none;
}
select:focus + .focus {
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border: 1px solid blue;
border-radius: inherit;
}
select:focus + .focus {
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border: 1px solid blue;
border-radius: inherit;
}
.cc-pagination-right {
height: 3rem;
}
.cc-pagination-right {
height: 3rem;
}
</style>

View File

@@ -1,16 +1,22 @@
<!--
@component Main plot component, based on uPlot; metricdata values by time
@component Job Data Compare Plot Component, based on uPlot; metricData values by jobId/startTime
Only width/height should change reactively.
Only width/height should change reactively.
Properties:
- `metric String`: The metric name
- `width Number?`: The plot width [Default: 0]
- `height Number?`: The plot height [Default: 300]
- `data [Array]`: The metric data object
- `cluster String`: Cluster name of the parent job / data
- `subCluster String`: Name of the subCluster of the parent job
-->
Properties:
- `metric String?`: The metric name [Default: ""]
- `width Number?`: The plot width [Default: 0]
- `height Number?`: The plot height [Default: 300]
- `data [Array]`: The data object [Default: null]
- `title String?`: Plot title [Default: ""]
- `xlabel String?`: Plot X axis label [Default: ""]
- `ylabel String?`: Plot Y axis label [Default: ""]
- `yunit String?`: Plot Y axis unit [Default: ""]
- `xticks Array`: Array containing jobIDs [Default: []]
- `xinfo Array`: Array containing job information [Default: []]
- `forResources Bool?`: Render this plot for allocated jobResources [Default: false]
- `plot Sync Object!`: uPlot cursor synchronization key
-->
<script>
import uPlot from "uplot";

View File

@@ -1,17 +1,20 @@
<!--
@component Histogram Plot based on uPlot Bars
@component Histogram Plot based on uPlot Bars
Properties:
- `data [[],[]]`: uPlot data structure array ( [[],[]] == [X, Y] )
- `usesBins Bool?`: If X-Axis labels are bins ("XX-YY") [Default: false]
- `width Number?`: Plot width (reactively adaptive) [Default: 500]
- `height Number?`: Plot height (reactively adaptive) [Default: 300]
- `title String?`: Plot title [Default: ""]
- `xlabel String?`: Plot X axis label [Default: ""]
- `xunit String?`: Plot X axis unit [Default: ""]
- `ylabel String?`: Plot Y axis label [Default: ""]
- `yunit String?`: Plot Y axis unit [Default: ""]
-->
Only width/height should change reactively.
Properties:
- `data [[],[]]`: uPlot data structure array ( [[],[]] == [X, Y] )
- `usesBins Bool?`: If X-Axis labels are bins ("XX-YY") [Default: false]
- `width Number?`: Plot width (reactively adaptive) [Default: null]
- `height Number?`: Plot height (reactively adaptive) [Default: 250]
- `title String?`: Plot title [Default: ""]
- `xlabel String?`: Plot X axis label [Default: ""]
- `xunit String?`: Plot X axis unit [Default: ""]
- `xtime Bool?`: If X-Axis is based on time information [Default: false]
- `ylabel String?`: Plot Y axis label [Default: ""]
- `yunit String?`: Plot Y axis unit [Default: ""]
-->
<script>
import uPlot from "uplot";

View File

@@ -1,24 +1,27 @@
<!--
@component Main plot component, based on uPlot; metricdata values by time
@component Main plot component, based on uPlot; metricdata values by time
Only width/height should change reactively.
Only width/height should change reactively.
Properties:
- `metric String`: The metric name
- `scope String?`: Scope of the displayed data [Default: node]
- `height Number?`: The plot height [Default: 300]
- `timestep Number`: The timestep used for X-axis rendering
- `series [GraphQL.Series]`: The metric data object
- `useStatsSeries Bool?`: If this plot uses the statistics Min/Max/Median representation; automatically set to according bool [Default: false]
- `statisticsSeries [GraphQL.StatisticsSeries]?`: Min/Max/Median representation of metric data [Default: null]
- `cluster String`: Cluster name of the parent job / data
- `subCluster String`: Name of the subCluster of the parent job
- `isShared Bool?`: If this job used shared resources; will adapt threshold indicators accordingly [Default: false]
- `forNode Bool?`: If this plot is used for node data display; will render x-axis as negative time with $now as maximum [Default: false]
- `numhwthreads Number?`: Number of job HWThreads [Default: 0]
- `numaccs Number?`: Number of job Accelerators [Default: 0]
- `zoomState Object?`: The last zoom state to preserve on user zoom [Default: null]
-->
Properties:
- `metric String`: The metric name
- `scope String?`: Scope of the displayed data [Default: node]
- `height Number?`: The plot height [Default: 300]
- `timestep Number`: The timestep used for X-axis rendering
- `series [GraphQL.Series]`: The metric data object
- `useStatsSeries Bool?`: If this plot uses the statistics Min/Max/Median representation; automatically set to according bool [Default: false]
- `statisticsSeries [GraphQL.StatisticsSeries]?`: Min/Max/Median representation of metric data [Default: null]
- `cluster String?`: Cluster name of the parent job / data [Default: ""]
- `subCluster String`: Name of the subCluster of the parent job
- `isShared Bool?`: If this job used shared resources; will adapt threshold indicators accordingly [Default: false]
- `forNode Bool?`: If this plot is used for node data display; will render x-axis as negative time with $now as maximum [Default: false]
- `numhwthreads Number?`: Number of job HWThreads [Default: 0]
- `numaccs Number?`: Number of job Accelerators [Default: 0]
- `zoomState Object?`: The last zoom state to preserve on user zoom [Default: null]
- `thersholdState Object?`: The last threshold state to preserve on user zoom [Default: null]
- `extendedLegendData Object?`: Additional information to be rendered in an extended legend [Default: null]
- `onZoom Func`: Callback function to handle zoom-in event
-->
<script>
import uPlot from "uplot";

View File

@@ -1,16 +1,17 @@
<!--
@component Pie Plot based on chart.js Pie
@component Pie Plot based on chart.js Pie
Properties:
- `size Number`: X and Y size of the plot, for square shape
- `sliceLabel String`: Label used in segment legends
- `quantities [Number]`: Data values
- `entities [String]`: Data identifiers
- `displayLegend?`: Display uPlot legend [Default: false]
Properties:
- `canvasId String?`: Unique ID for correct parallel chart.js rendering [Default: "pie-default"]
- `size Number`: X and Y size of the plot, for square shape
- `sliceLabel String`: Label used in segment legends
- `quantities [Number]`: Data values
- `entities [String]`: Data identifiers
- `displayLegend?`: Display uPlot legend [Default: false]
Exported:
- `colors ['rgb(x,y,z)', ...]`: Color range used for segments; upstream used for legend
-->
Exported:
- `colors ['rgb(x,y,z)', ...]`: Color range used for segments; upstream used for legend
-->
<script module>
// http://tsitsul.in/blog/coloropt/ : 12 colors normal

View File

@@ -1,11 +1,12 @@
<!--
@component Polar Plot based on chart.js Radar
@component Polar Plot based on chart.js Radar
Properties:
- `polarMetrics [Object]?`: Metric names and scaled peak values for rendering polar plot [Default: [] ]
- `polarData [GraphQL.JobMetricStatWithName]?`: Metric data [Default: null]
- `height Number?`: Plot height [Default: 365]
-->
Properties:
- `polarMetrics [Object]?`: Metric names and scaled peak values for rendering polar plot [Default: [] ]
- `polarData [GraphQL.JobMetricStatWithName]?`: Metric data [Default: null]
- `canvasId String?`: Unique ID for correct parallel chart.js rendering [Default: "polar-default"]
- `height Number?`: Plot height [Default: 365]
-->
<script>
import { onMount } from 'svelte'

View File

@@ -1,25 +1,25 @@
<!--
@component Roofline Model Plot based on uPlot
@component Roofline Model Plot based on uPlot
Properties:
- `data [null, [], []]`: Roofline Data Structure, see below for details [Default: null]
- `renderTime Bool?`: If time information should be rendered as colored dots [Default: false]
- `allowSizeChange Bool?`: If dimensions of rendered plot can change [Default: false]
- `subCluster GraphQL.SubCluster?`: SubCluster Object; contains required topology information [Default: null]
- `width Number?`: Plot width (reactively adaptive) [Default: 600]
- `height Number?`: Plot height (reactively adaptive) [Default: 380]
Properties:
- `data [null, [], []]`: Roofline Data Structure, see below for details [Default: null]
- `renderTime Bool?`: If time information should be rendered as colored dots [Default: false]
- `allowSizeChange Bool?`: If dimensions of rendered plot can change [Default: false]
- `subCluster GraphQL.SubCluster?`: SubCluster Object; contains required topology information [Default: null]
- `width Number?`: Plot width (reactively adaptive) [Default: 600]
- `height Number?`: Plot height (reactively adaptive) [Default: 380]
Data Format:
- `data = [null, [], []]`
- Index 0: null-axis required for scatter
- Index 1: Array of XY-Arrays for Scatter
- Index 2: Optional Time Info
- `data[1][0] = [100, 200, 500, ...]`
- X Axis: Intensity (Vals up to clusters' flopRateScalar value)
- `data[1][1] = [1000, 2000, 1500, ...]`
- Y Axis: Performance (Vals up to clusters' flopRateSimd value)
- `data[2] = [0.1, 0.15, 0.2, ...]`
- Color Code: Time Information (Floats from 0 to 1) (Optional)
- `data = [null, [], []]`
- Index 0: null-axis required for scatter
- Index 1: Array of XY-Arrays for Scatter
- Index 2: Optional Time Info
- `data[1][0] = [100, 200, 500, ...]`
- X Axis: Intensity (Vals up to clusters' flopRateScalar value)
- `data[1][1] = [1000, 2000, 1500, ...]`
- Y Axis: Performance (Vals up to clusters' flopRateSimd value)
- `data[2] = [0.1, 0.15, 0.2, ...]`
- Color Code: Time Information (Floats from 0 to 1) (Optional)
-->
<script>

View File

@@ -5,12 +5,13 @@
- `X [Number]`: Data from first selected metric as X-values
- `Y [Number]`: Data from second selected metric as Y-values
- `S GraphQl.TimeWeights.X?`: Float to scale the data with [Default: null]
- `color String`: Color of the drawn scatter circles
- `width Number`:
- `height Number`:
- `xLabel String`:
- `yLabel String`:
-->
- `color String?`: Color of the drawn scatter circles [Default: '#0066cc']
- `width Number?`: Width of the plot [Default: 250]
- `height Number?`: Height of the plot [Default: 300]
- `xLabel String?`: X-Axis Label [Ðefault: ""]
- `yLabel String?`: Y-Axis Label [Default: ""]
-->
<script>
import { onMount } from 'svelte';
import { formatNumber } from '../units.js'
@@ -23,8 +24,8 @@
color = '#0066cc',
width = 250,
height = 300,
xLabel,
yLabel,
xLabel = "",
yLabel = "",
} = $props();
/* Const Init */

View File

@@ -1,259 +1,257 @@
<!--
Copyright (c) 2021 Michael Keller
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 #2: Rewritten for Svelte 5, removed bodyHandler
Copyright (c) 2021 Michael Keller
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 #2: Rewritten for Svelte 5, removed bodyHandler
-->
<!--
@component Selector component to display range selections via min and max double-sliders
Properties:
- min: Number
- max: Number
- sliderHandleFrom: Number (Starting position of slider #1)
- sliderHandleTo: Number (Starting position of slider #2)
Events:
- `change`: [Number, Number] (Positions of the two sliders)
<!--
@component Selector component to display range selections via min and max double-sliders
Properties:
- `sliderMin Number!`: Minimum possible value for slider
- `sliderMax Number!`: Maximum possible value for slider
- `fromPreset Number?`: Latest "from" value selection [Default: 1]
- `toPreset Number?`: Latest "to" value selection [Default: 100]
- `changeRange Func`: The callback function to apply current range selections
-->
<script>
/* Svelte 5 Props */
let {
sliderMin,
sliderMax,
fromPreset = 1,
toPreset = 100,
changeRange
} = $props();
/* Svelte 5 Props */
let {
sliderMin,
sliderMax,
fromPreset = 1,
toPreset = 100,
changeRange
} = $props();
/* State Init */
let pendingValues = $state([fromPreset, toPreset]);
let sliderFrom = $state(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
let sliderTo = $state(Math.min(((toPreset == null ? sliderMin : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
let inputFieldFrom = $state(fromPreset.toString());
let inputFieldTo = $state(toPreset.toString());
let leftHandle = $state();
let sliderMain = $state();
/* State Init */
let pendingValues = $state([fromPreset, toPreset]);
let sliderFrom = $state(Math.max(((fromPreset == null ? sliderMin : fromPreset) - sliderMin) / (sliderMax - sliderMin), 0.));
let sliderTo = $state(Math.min(((toPreset == null ? sliderMin : toPreset) - sliderMin) / (sliderMax - sliderMin), 1.));
let inputFieldFrom = $state(fromPreset.toString());
let inputFieldTo = $state(toPreset.toString());
let leftHandle = $state();
let sliderMain = $state();
/* Var Init */
let timeoutId = null;
/* Var Init */
let timeoutId = null;
/* Functions */
function queueChangeEvent() {
if (timeoutId !== null) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
timeoutId = null
changeRange(pendingValues);
}, 100);
}
/* Functions */
function queueChangeEvent() {
if (timeoutId !== null) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
timeoutId = null
changeRange(pendingValues);
}, 100);
}
function updateStates(newValue, newPosition, target) {
if (target === 'from') {
pendingValues[0] = isNaN(newValue) ? null : newValue;
inputFieldFrom = isNaN(newValue) ? null : newValue.toString();
sliderFrom = newPosition;
} else if (target === 'to') {
pendingValues[1] = isNaN(newValue) ? null : newValue;
inputFieldTo = isNaN(newValue) ? null : newValue.toString();
sliderTo = newPosition;
}
function updateStates(newValue, newPosition, target) {
if (target === 'from') {
pendingValues[0] = isNaN(newValue) ? null : newValue;
inputFieldFrom = isNaN(newValue) ? null : newValue.toString();
sliderFrom = newPosition;
} else if (target === 'to') {
pendingValues[1] = isNaN(newValue) ? null : newValue;
inputFieldTo = isNaN(newValue) ? null : newValue.toString();
sliderTo = newPosition;
}
queueChangeEvent();
}
queueChangeEvent();
}
function rangeChanged (evt, target) {
evt.preventDefault()
evt.stopPropagation()
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 rangeChanged (evt, target) {
evt.preventDefault()
evt.stopPropagation()
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 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 clamp(x, testMin, testMax) {
return x < testMin
? testMin
: (x > testMax
? testMax
: x
);
}
function draggable(node) {
let x;
let y;
function draggable(node) {
let x;
let y;
function handleMousedown(event) {
if (event.type === 'touchstart') {
event = event.touches[0];
}
x = event.clientX;
y = event.clientY;
function handleMousedown(event) {
if (event.type === 'touchstart') {
event = event.touches[0];
}
x = event.clientX;
y = event.clientY;
node.dispatchEvent(new CustomEvent('dragstart', {
detail: { x, y }
}));
node.dispatchEvent(new CustomEvent('dragstart', {
detail: { x, y }
}));
window.addEventListener('mousemove', handleMousemove);
window.addEventListener('mouseup', handleMouseup);
window.addEventListener('mousemove', handleMousemove);
window.addEventListener('mouseup', handleMouseup);
window.addEventListener('touchmove', handleMousemove);
window.addEventListener('touchend', handleMouseup);
}
window.addEventListener('touchmove', handleMousemove);
window.addEventListener('touchend', handleMouseup);
}
function handleMousemove(event) {
if (event.type === 'touchmove') {
event = event.changedTouches[0];
}
function handleMousemove(event) {
if (event.type === 'touchmove') {
event = event.changedTouches[0];
}
const dx = event.clientX - x;
const dy = event.clientY - y;
const dx = event.clientX - x;
const dy = event.clientY - y;
x = event.clientX;
y = event.clientY;
x = event.clientX;
y = event.clientY;
node.dispatchEvent(new CustomEvent('dragmove', {
detail: { x, y, dx, dy }
}));
}
node.dispatchEvent(new CustomEvent('dragmove', {
detail: { x, y, dx, dy }
}));
}
function handleMouseup(event) {
x = event.clientX;
y = event.clientY;
function handleMouseup(event) {
x = event.clientX;
y = event.clientY;
node.dispatchEvent(new CustomEvent('dragend', {
detail: { x, y }
}));
node.dispatchEvent(new CustomEvent('dragend', {
detail: { x, y }
}));
window.removeEventListener('mousemove', handleMousemove);
window.removeEventListener('mouseup', handleMouseup);
window.removeEventListener('mousemove', handleMousemove);
window.removeEventListener('mouseup', handleMouseup);
window.removeEventListener('touchmove', handleMousemove);
window.removeEventListener('touchend', handleMouseup);
}
window.removeEventListener('touchmove', handleMousemove);
window.removeEventListener('touchend', handleMouseup);
}
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('touchstart', handleMousedown);
return {
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('touchstart', handleMousedown);
}
};
}
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('touchstart', handleMousedown);
return {
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('touchstart', handleMousedown);
}
};
}
</script>
<div class="double-range-container">
<div class="header">
<input class="form-control" type="text" placeholder="from..." value={inputFieldFrom}
oninput={(e) => inputChanged(e, 'from')} />
<div class="header">
<input class="form-control" type="text" placeholder="from..." value={inputFieldFrom}
oninput={(e) => inputChanged(e, 'from')} />
<span>Full Range: <b> {sliderMin} </b> - <b> {sliderMax} </b></span>
<span>Full Range: <b> {sliderMin} </b> - <b> {sliderMax} </b></span>
<input class="form-control" type="text" placeholder="to..." value={inputFieldTo}
oninput={(e) => inputChanged(e, 'to')} />
</div>
<input class="form-control" type="text" placeholder="to..." value={inputFieldTo}
oninput={(e) => inputChanged(e, 'to')} />
</div>
<div id="slider-active" class="slider" bind:this={sliderMain}>
<div
class="slider-body"
style="left: {100 * sliderFrom}%;right: {100 * (1 - sliderTo)}%;"
></div>
<div
class="slider-handle"
bind:this={leftHandle}
data-which="from"
use:draggable
ondragmove={(e) => rangeChanged(e, 'from')}
style="left: {100 * sliderFrom}%"
></div>
<div
class="slider-handle"
data-which="to"
use:draggable
ondragmove={(e) => rangeChanged(e, 'to')}
style="left: {100 * sliderTo}%"
></div>
</div>
<div id="slider-active" class="slider" bind:this={sliderMain}>
<div
class="slider-body"
style="left: {100 * sliderFrom}%;right: {100 * (1 - sliderTo)}%;"
></div>
<div
class="slider-handle"
bind:this={leftHandle}
data-which="from"
use:draggable
ondragmove={(e) => rangeChanged(e, 'from')}
style="left: {100 * sliderFrom}%"
></div>
<div
class="slider-handle"
data-which="to"
use:draggable
ondragmove={(e) => rangeChanged(e, 'to')}
style="left: {100 * sliderTo}%"
></div>
</div>
</div>
<style>
.header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 5px;
}
.header :nth-child(2) {
padding-top: 10px;
}
.header input {
height: 25px;
border-radius: 5px;
width: 100px;
}
.header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 5px;
}
.header :nth-child(2) {
padding-top: 10px;
}
.header input {
height: 25px;
border-radius: 5px;
width: 100px;
}
.double-range-container {
width: 100%;
height: 50px;
user-select: none;
box-sizing: border-box;
white-space: nowrap;
margin-top: -4px;
}
.slider {
position: relative;
width: 100%;
height: 6px;
top: 10px;
transform: translate(0, -50%);
background-color: #e2e2e2;
box-shadow: inset 0 7px 10px -5px #4a4a4a, inset 0 -1px 0px 0px #9c9c9c;
border-radius: 6px;
}
.slider-handle {
position: absolute;
top: 50%;
width: 0;
height: 0;
}
.slider-handle:after {
content: ' ';
box-sizing: border-box;
position: absolute;
border-radius: 50%;
width: 16px;
height: 16px;
background-color: #fdfdfd;
border: 1px solid #7b7b7b;
transform: translate(-50%, -50%)
}
/* .handle[data-which="end"]:after{
transform: translate(-100%, -50%);
} */
.slider-handle:active:after {
background-color: #ddd;
z-index: 9;
}
.slider-body {
top: 0;
position: absolute;
background-color: #34a1ff;
bottom: 0;
}
.double-range-container {
width: 100%;
height: 50px;
user-select: none;
box-sizing: border-box;
white-space: nowrap;
margin-top: -4px;
}
.slider {
position: relative;
width: 100%;
height: 6px;
top: 10px;
transform: translate(0, -50%);
background-color: #e2e2e2;
box-shadow: inset 0 7px 10px -5px #4a4a4a, inset 0 -1px 0px 0px #9c9c9c;
border-radius: 6px;
}
.slider-handle {
position: absolute;
top: 50%;
width: 0;
height: 0;
}
.slider-handle:after {
content: ' ';
box-sizing: border-box;
position: absolute;
border-radius: 50%;
width: 16px;
height: 16px;
background-color: #fdfdfd;
border: 1px solid #7b7b7b;
transform: translate(-50%, -50%)
}
/* .handle[data-which="end"]:after{
transform: translate(-100%, -50%);
} */
.slider-handle:active:after {
background-color: #ddd;
z-index: 9;
}
.slider-body {
top: 0;
position: absolute;
background-color: #34a1ff;
bottom: 0;
}
</style>

View File

@@ -1,11 +1,12 @@
<!--
@component Selector component for (footprint) metrics to be displayed as histogram
@component Selector component for (footprint) metrics to be displayed as histogram
Properties:
- `cluster String`: Currently selected cluster
- `selectedHistograms [String]`: The currently selected metrics to display as histogram
- ìsOpen Bool`: Is selection opened
-->
Properties:
- `cluster String`: Currently selected cluster
- `selectedHistograms [String]`: The currently selected metrics to display as histogram
- `ìsOpen Bool`: Is selection opened [Bindable]
- `applyChange Func`: The callback function to apply current selection
-->
<script>
import { getContext } from "svelte";

View File

@@ -1,15 +1,18 @@
<!--
@component Metric selector component; allows reorder via drag and drop
@component Metric selector component; allows reorder via drag and drop
Properties:
- `metrics [String]`: (changes from inside, needs to be initialised, list of selected metrics)
- `isOpen Bool`: (can change from inside and outside)
- `configName String`: The config key for the last saved selection (constant)
- `allMetrics [String]?`: List of all available metrics [Default: null]
- `cluster String?`: The currently selected cluster [Default: null]
- `showFootprint Bool?`: Upstream state of wether to render footpritn card [Default: false]
- `footprintSelect Bool?`: Render checkbox for footprint display in upstream component [Default: false]
-->
Properties:
- `isOpen Bool`: Is selection modal opened [Bindable, Default: false]
- `showFootprint Bool?`: Upstream state of whether to render footprint card [Bindable, Default: false]
- `totalMetrics Number?`: Total available metrics [Bindable, Default: 0]
- `presetMetrics [String]`: Latest selection of metrics [Default: []]
- `cluster String?`: The currently selected cluster [Default: null]
- `subCluster String?`: The currently selected subCluster [Default: null]
- `footprintSelect Bool?`: Render checkbox for footprint display in upstream component [Default: false]
- `preInitialized Bool?`: If the parent component has a dedicated call to init() [Default: false]
- `configName String`: The config key for the last saved selection (constant)
- `applyMetrics Func`: The callback function to apply current selection
-->
<script>
import { getContext } from "svelte";
@@ -64,7 +67,7 @@
/* Reactive Effects */
$effect(() => {
totalMetrics = allMetrics.size;
totalMetrics = allMetrics?.size || 0;
});
$effect(() => {

View File

@@ -1,10 +1,12 @@
<!--
@component Selector for sorting field and direction
@component Selector for sorting field and direction
Properties:
- sorting: { field: String, order: "DESC" | "ASC" } (changes from inside)
- isOpen: Boolean (can change from inside and outside)
-->
Properties:
- `presetSorting Object?`: The latest sort selection state
- Default { field: "startTime", type: "col", order: "DESC" }
- `isOpen Bool?`: Is modal opened [Bindable, Default: false]
- `applySorting Func`: The callback function to apply current selection
-->
<script>
import { getContext, onMount } from "svelte";

View File

@@ -1,15 +1,13 @@
<!--
@component Selector for specified real time ranges for data cutoff; used in systems and nodes view
@component Selector for specified real time ranges for data cutoff; used in systems and nodes view
Properties:
- `from Date`: The datetime to start data display from
- `to Date`: The datetime to end data display at
- `customEnabled Bool?`: Allow custom time window selection [Default: true]
- `options Object? {String:Number}`: The quick time selection options [Default: {..., "Last 24hrs": 24*60*60}]
Events:
- `change, {Date, Date}`: Set 'from, to' values in upstream component
-->
Properties:
- `presetFrom Date`: The latest "from" JS Date Object
- `presetTo Date`: The latest Date "to" JS Date Object
- `customEnabled Bool?`: Allow custom time window selection [Default: true]
- `options Object? {String:Number}`: The quick time selection options [Default: {..., "Last 24hrs": 24*60*60}]
- `applyTime Func`: The callback function to apply current selection
-->
<script>
import {