mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-10-27 14:45:06 +01:00
Update component header, format, streamline SV5 components
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<!--
|
||||
@component Main analysis view component
|
||||
@component Main analysis view component
|
||||
|
||||
Properties:
|
||||
- `filterPresets Object`: Optional predefined filter values
|
||||
-->
|
||||
Properties:
|
||||
- `filterPresets Object`: Optional predefined filter values
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext, onMount } from "svelte";
|
||||
@@ -38,7 +38,9 @@
|
||||
import RooflineHeatmap from "./generic/plots/RooflineHeatmap.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { filterPresets } = $props();
|
||||
let {
|
||||
filterPresets
|
||||
} = $props();
|
||||
|
||||
// By default, look at the jobs of the last 6 hours:
|
||||
if (filterPresets?.startTime == null) {
|
||||
@@ -346,8 +348,10 @@
|
||||
{:else if cluster}
|
||||
<PlotSelection
|
||||
availableMetrics={availableMetrics.map((av) => av.name)}
|
||||
bind:metricsInHistograms
|
||||
bind:metricsInScatterplots
|
||||
presetMetricsInHistograms={metricsInHistograms}
|
||||
presetMetricsInScatterplots={metricsInScatterplots}
|
||||
applyHistograms={(metrics) => metricsInHistograms = [...metrics]}
|
||||
applyScatter={(metrics) => metricsInScatterplots = [...metrics]}
|
||||
/>
|
||||
{/if}
|
||||
</Col>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<!--
|
||||
@component Main Config Option Component, Wrapper for admin and user sub-components
|
||||
@component Main Config Option Component, Wrapper for admin and user sub-components
|
||||
|
||||
Properties:
|
||||
- `ìsAdmin Bool!`: Is currently logged in user admin authority
|
||||
- `isSupport Bool!`: Is currently logged in user support authority
|
||||
- `isApi Bool!`: Is currently logged in user api authority
|
||||
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
|
||||
-->
|
||||
Properties:
|
||||
- `ìsAdmin Bool!`: Is currently logged in user admin authority
|
||||
- `isSupport Bool!`: Is currently logged in user support authority
|
||||
- `isApi Bool!`: Is currently logged in user api authority
|
||||
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
|
||||
- `ncontent String!`: The currently displayed message on the homescreen
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Card, CardHeader, CardTitle } from "@sveltestrap/sveltestrap";
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<!--
|
||||
@component Main navbar component; handles view display based on user roles
|
||||
@component Main navbar component; handles view display based on user roles
|
||||
|
||||
Properties:
|
||||
- `username String`: Empty string if auth. is disabled, otherwise the username as string
|
||||
- `authlevel Number`: The current users authentication level
|
||||
- `clusters [String]`: List of cluster names
|
||||
- `subClusters [String]`: List of subCluster names
|
||||
- `roles [Number]`: Enum containing available roles
|
||||
-->
|
||||
Properties:
|
||||
- `username String`: Empty string if auth. is disabled, otherwise the username as string
|
||||
- `authlevel Number`: The current users authentication level
|
||||
- `clusters [String]`: List of cluster names
|
||||
- `subClusters [String]`: List of subCluster names
|
||||
- `roles [Number]`: Enum containing available roles
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
@@ -25,7 +25,13 @@
|
||||
import NavbarTools from "./header/NavbarTools.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { username, authlevel, clusters, subClusters, roles } = $props();
|
||||
let {
|
||||
username,
|
||||
authlevel,
|
||||
clusters,
|
||||
subClusters,
|
||||
roles
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const jobsTitle = new Map();
|
||||
@@ -123,11 +129,11 @@
|
||||
let isOpen = $state(false);
|
||||
let screenSize = $state(0);
|
||||
|
||||
/* Derived Vars */
|
||||
let showMax = $derived(screenSize >= 1500);
|
||||
let showMid = $derived(screenSize < 1500 && screenSize >= 1300);
|
||||
let showSml = $derived(screenSize < 1300 && screenSize >= 768);
|
||||
let showBrg = $derived(screenSize < 768);
|
||||
/* Derived */
|
||||
const showMax = $derived(screenSize >= 1500);
|
||||
const showMid = $derived(screenSize < 1500 && screenSize >= 1300);
|
||||
const showSml = $derived(screenSize < 1300 && screenSize >= 768);
|
||||
const showBrg = $derived(screenSize < 768);
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth={screenSize} />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!--
|
||||
@component Main single job display component; displays plots for every metric as well as various information
|
||||
@component Main single job display component; displays plots for every metric as well as various information
|
||||
|
||||
Properties:
|
||||
- `dbid Number`: The jobs DB ID
|
||||
- `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
|
||||
-->
|
||||
Properties:
|
||||
- `dbid Number`: The jobs DB ID
|
||||
- `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
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
@@ -156,9 +156,9 @@
|
||||
|
||||
/* Effects */
|
||||
$effect(() => {
|
||||
document.title = $initq.fetching
|
||||
document.title = $initq?.fetching
|
||||
? "Loading..."
|
||||
: $initq.error
|
||||
: $initq?.error
|
||||
? "Error"
|
||||
: `Job ${$initq.data.job.jobId} - ClusterCockpit`;
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!--
|
||||
@component Main job list component
|
||||
@component Main job list component
|
||||
|
||||
Properties:
|
||||
- `filterPresets Object`: Optional predefined filter values
|
||||
- `authlevel Number`: The current users authentication level
|
||||
- `roles [Number]`: Enum containing available roles
|
||||
-->
|
||||
|
||||
Properties:
|
||||
- `filterPresets Object?`: Optional predefined filter values [Default: {}]
|
||||
- `authlevel Number`: The current users authentication level
|
||||
- `roles [Number]`: Enum containing available roles
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { untrack, onMount, getContext } from "svelte";
|
||||
import {
|
||||
@@ -28,7 +28,11 @@
|
||||
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { filterPresets, authlevel, roles } = $props();
|
||||
let {
|
||||
filterPresets,
|
||||
authlevel,
|
||||
roles
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const { query: initq } = init();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!--
|
||||
@component Main component for listing users or projects
|
||||
@component Main component for listing users or projects
|
||||
|
||||
Properties:
|
||||
- `type String?`: The type of list ['USER' || 'PROJECT']
|
||||
- `filterPresets Object?`: Optional predefined filter values [Default: {}]
|
||||
-->
|
||||
Properties:
|
||||
- `type String?`: The type of list ['USER' || 'PROJECT']
|
||||
- `filterPresets Object?`: Optional predefined filter values [Default: {}]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
@@ -32,18 +32,12 @@
|
||||
import Filters from "./generic/Filters.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { type, filterPresets } = $props();
|
||||
|
||||
// By default, look at the jobs of the last 30 days:
|
||||
if (filterPresets?.startTime == null) {
|
||||
if (filterPresets == null) filterPresets = {};
|
||||
|
||||
filterPresets.startTime = {
|
||||
range: "last30d",
|
||||
text: "Last 30 Days",
|
||||
};
|
||||
}
|
||||
let {
|
||||
type,
|
||||
filterPresets
|
||||
} = $props();
|
||||
|
||||
/* Validate Type */
|
||||
console.assert(
|
||||
type == "USER" || type == "PROJECT",
|
||||
"Invalid list type provided!",
|
||||
@@ -64,16 +58,16 @@
|
||||
queryStore({
|
||||
client: client,
|
||||
query: gql`
|
||||
query($jobFilters: [JobFilter!]!) {
|
||||
rows: jobsStatistics(filter: $jobFilters, groupBy: ${type}) {
|
||||
id
|
||||
name
|
||||
totalJobs
|
||||
totalWalltime
|
||||
totalCoreHours
|
||||
totalAccHours
|
||||
}
|
||||
}`,
|
||||
query($jobFilters: [JobFilter!]!) {
|
||||
rows: jobsStatistics(filter: $jobFilters, groupBy: ${type}) {
|
||||
id
|
||||
name
|
||||
totalJobs
|
||||
totalWalltime
|
||||
totalCoreHours
|
||||
totalAccHours
|
||||
}
|
||||
}`,
|
||||
variables: { jobFilters },
|
||||
})
|
||||
);
|
||||
@@ -107,7 +101,19 @@
|
||||
}
|
||||
|
||||
/* On Mount */
|
||||
onMount(() => filterComponent.updateFilters());
|
||||
onMount(() => {
|
||||
// By default, look at the jobs of the last 30 days:
|
||||
if (filterPresets?.startTime == null) {
|
||||
if (filterPresets == null) filterPresets = {};
|
||||
|
||||
filterPresets.startTime = {
|
||||
range: "last30d",
|
||||
text: "Last 30 Days",
|
||||
};
|
||||
};
|
||||
// Init Filter
|
||||
filterComponent.updateFilters();
|
||||
});
|
||||
</script>
|
||||
|
||||
<Row cols={{ xs: 1, md: 2}}>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!--
|
||||
@component System-View subcomponent; renders all current metrics for specified node
|
||||
@component System-View subcomponent; renders all current metrics for specified node
|
||||
|
||||
Properties:
|
||||
- `cluster String`: Currently selected cluster
|
||||
- `hostname String`: Currently selected host (== node)
|
||||
- `from Date?`: Custom Time Range selection 'from' [Default: null]
|
||||
- `to Date?`: Custom Time Range selection 'to' [Default: null]
|
||||
-->
|
||||
Properties:
|
||||
- `cluster String`: Currently selected cluster
|
||||
- `hostname String`: Currently selected host (== node)
|
||||
- `presetFrom Date?`: Custom Time Range selection 'from' [Default: null]
|
||||
- `presetTo Date?`: Custom Time Range selection 'to' [Default: null]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!--
|
||||
@component Main cluster status view component; renders current system-usage information
|
||||
@component Main cluster status view component; renders current system-usage information
|
||||
|
||||
Properties:
|
||||
- `cluster String`: The cluster to show status information for
|
||||
-->
|
||||
|
||||
Properties:
|
||||
- `cluster String`: The cluster to show status information for
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import {
|
||||
@@ -43,7 +43,9 @@
|
||||
import HistogramSelection from "./generic/select/HistogramSelection.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { cluster } = $props();
|
||||
let {
|
||||
cluster
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const { query: initq } = init();
|
||||
@@ -60,9 +62,13 @@
|
||||
/* State Init */
|
||||
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
||||
let to = $state(new Date(Date.now()));
|
||||
let isHistogramSelectionOpen = $state(false);
|
||||
let colWidth = $state(0);
|
||||
let plotWidths = $state([]);
|
||||
// Histrogram
|
||||
let isHistogramSelectionOpen = $state(false);
|
||||
let selectedHistograms = $state(cluster
|
||||
? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
||||
: ccconfig['user_view_histogramMetrics'] || []);
|
||||
// Bar Gauges
|
||||
let allocatedNodes = $state({});
|
||||
let flopRate = $state({});
|
||||
@@ -71,11 +77,7 @@
|
||||
let memBwRate = $state({});
|
||||
let memBwRateUnitPrefix = $state({});
|
||||
let memBwRateUnitBase = $state({});
|
||||
|
||||
let selectedHistograms = $state(cluster
|
||||
? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
||||
: ccconfig['user_view_histogramMetrics'] || []);
|
||||
|
||||
// Pie Charts
|
||||
let topProjectSelection = $state(
|
||||
topOptions.find(
|
||||
(option) =>
|
||||
@@ -86,7 +88,6 @@
|
||||
(option) => option.key == ccconfig.status_view_selectedTopProjectCategory,
|
||||
)
|
||||
);
|
||||
|
||||
let topUserSelection = $state(
|
||||
topOptions.find(
|
||||
(option) =>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<!--
|
||||
@component Main cluster node status view component; renders overview or list depending on type
|
||||
@component Main cluster node status view component; renders overview or list depending on type
|
||||
|
||||
Properties:
|
||||
- `displayType String?`: The type of node display ['OVERVIEW' || 'LIST']
|
||||
- `cluster String`: The cluster to show status information for
|
||||
- `from Date?`: Custom Time Range selection 'from' [Default: null]
|
||||
- `to Date?`: Custom Time Range selection 'to' [Default: null]
|
||||
-->
|
||||
Properties:
|
||||
- `displayType String?`: The type of node display ['OVERVIEW' || 'LIST']
|
||||
- `cluster String`: The cluster to show status information for [Default: null]
|
||||
- `subCluster String`: The subCluster to show status information for [Default: null]
|
||||
- `presetFrom Date?`: Custom Time Range selection 'from' [Default: null]
|
||||
- `presetTo Date?`: Custom Time Range selection 'to' [Default: null]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
@@ -38,8 +39,8 @@
|
||||
displayType,
|
||||
cluster = null,
|
||||
subCluster = null,
|
||||
fromPreset = null,
|
||||
toPreset = null,
|
||||
presetFrom = null,
|
||||
presetTo = null,
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
@@ -54,9 +55,12 @@
|
||||
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
|
||||
const nowDate = new Date(Date.now());
|
||||
|
||||
/* Var Init */
|
||||
let timeoutId = null;
|
||||
|
||||
/* State Init */
|
||||
let to = $state(toPreset || new Date(Date.now()));
|
||||
let from = $state(fromPreset || new Date(nowDate.setHours(nowDate.getHours() - 4)));
|
||||
let to = $state(presetTo || new Date(Date.now()));
|
||||
let from = $state(presetFrom || new Date(nowDate.setHours(nowDate.getHours() - 4)));
|
||||
let selectedResolution = $state(resampleConfig ? resampleDefault : 0);
|
||||
let hostnameFilter = $state("");
|
||||
let pendingHostnameFilter = $state("");
|
||||
@@ -89,7 +93,6 @@
|
||||
};
|
||||
|
||||
// Wait after input for some time to prevent too many requests
|
||||
let timeoutId = null;
|
||||
function updateHostnameFilter() {
|
||||
if (timeoutId != null) clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(function () {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
@component Tag List Svelte Component. Displays All Tags, Allows deletion.
|
||||
@component Tag List Svelte Component. Displays All Tags, Allows deletion.
|
||||
|
||||
Properties:
|
||||
- `username String!`: Users username.
|
||||
- `isAdmin Bool!`: User has Admin Auth.
|
||||
- `tagmap Object!`: Map of accessible, appwide tags. Prefiltered in backend.
|
||||
-->
|
||||
Properties:
|
||||
- `username String!`: Users username.
|
||||
- `isAdmin Bool!`: User has Admin Auth.
|
||||
- `presetTagmap Object!`: Map of accessible, appwide tags. Prefiltered in backend.
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
/* State Init */
|
||||
let pendingChange = $state("none");
|
||||
let tagmap = $state(presetTagmap)
|
||||
let tagmap = $state(presetTagmap);
|
||||
|
||||
/* Functions */
|
||||
const removeTagMutation = ({ tagIds }) => {
|
||||
@@ -68,8 +68,8 @@
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!--
|
||||
@component Main user jobs list display component; displays job list and additional information for a given user
|
||||
@component Main user jobs list display component; displays job list and additional information for a given user
|
||||
|
||||
Properties:
|
||||
- `user Object`: The GraphQL user object
|
||||
- `filterPresets Object`: Optional predefined filter values
|
||||
-->
|
||||
Properties:
|
||||
- `user Object`: The GraphQL user object
|
||||
- `filterPresets Object`: Optional predefined filter values
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { onMount, getContext } from "svelte";
|
||||
@@ -43,7 +43,10 @@
|
||||
import Refresher from "./generic/helper/Refresher.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { user, filterPresets } = $props();
|
||||
let {
|
||||
user,
|
||||
filterPresets
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const { query: initq } = init();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<!--
|
||||
@component Analysis-View subcomponent; allows selection for normalized histograms and scatterplots
|
||||
@component Analysis-View subcomponent; allows selection for normalized histograms and scatterplots
|
||||
|
||||
Properties:
|
||||
- `availableMetrics [String]`: Available metrics in selected cluster
|
||||
- `metricsInHistograms [String]`: The currently selected metrics to display as histogram
|
||||
- `metricsInScatterplots [[String, String]]`: The currently selected metrics to display as scatterplot
|
||||
-->
|
||||
Properties:
|
||||
- `availableMetrics [String]`: Available metrics in selected cluster
|
||||
- `presetMetricsInHistograms [String]`: The latest selected metrics to display as histogram
|
||||
- `presetMetricsInScatterplots [[String, String]]`: The latest selected metrics to display as scatterplot
|
||||
- `applyHistograms Func`: The callback function to apply current histogramMetrics selection
|
||||
- `applyScatter Func`: The callback function to apply current scatterMetrics selection
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
@@ -24,8 +26,10 @@
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
availableMetrics,
|
||||
metricsInHistograms = $bindable(),
|
||||
metricsInScatterplots = $bindable(),
|
||||
presetMetricsInHistograms,
|
||||
presetMetricsInScatterplots,
|
||||
applyHistograms,
|
||||
applyScatter
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
@@ -45,6 +49,8 @@
|
||||
/* State Init */
|
||||
let isHistogramConfigOpen = $state(false);
|
||||
let isScatterPlotConfigOpen = $state(false);
|
||||
let metricsInHistograms = $state(presetMetricsInHistograms);
|
||||
let metricsInScatterplots = $state(presetMetricsInScatterplots);
|
||||
let selectedMetric1 = $state(null);
|
||||
let selectedMetric2 = $state(null);
|
||||
|
||||
@@ -84,11 +90,13 @@
|
||||
type="checkbox"
|
||||
bind:group={metricsInHistograms}
|
||||
value={metric}
|
||||
onchange={() =>
|
||||
onchange={() => {
|
||||
updateConfiguration({
|
||||
name: "analysis_view_histogramMetrics",
|
||||
value: metricsInHistograms,
|
||||
})}
|
||||
});
|
||||
applyHistograms(metricsInHistograms);
|
||||
}}
|
||||
/>
|
||||
|
||||
{metric}
|
||||
@@ -126,6 +134,7 @@
|
||||
name: "analysis_view_scatterPlotMetrics",
|
||||
value: metricsInScatterplots,
|
||||
});
|
||||
applyScatter(metricsInScatterplots);
|
||||
}}
|
||||
>
|
||||
<Icon name="x" />
|
||||
@@ -163,6 +172,7 @@
|
||||
name: "analysis_view_scatterPlotMetrics",
|
||||
value: metricsInScatterplots,
|
||||
});
|
||||
applyScatter(metricsInScatterplots);
|
||||
}}
|
||||
>
|
||||
Add Plot
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<!--
|
||||
@component Admin settings wrapper
|
||||
-->
|
||||
@component Admin settings wrapper
|
||||
|
||||
Properties:
|
||||
- `ncontent String`: The homepage notice content
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Row, Col } from "@sveltestrap/sveltestrap";
|
||||
@@ -13,7 +16,9 @@
|
||||
import NoticeEdit from "./admin/NoticeEdit.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { ncontent } = $props();
|
||||
let {
|
||||
ncontent
|
||||
} = $props();
|
||||
|
||||
/* Const Init*/
|
||||
const ccconfig = getContext("cc-config");
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!--
|
||||
@component Support settings wrapper
|
||||
Properties: None
|
||||
-->
|
||||
@component Support settings wrapper
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import SupportOptions from "./support/SupportOptions.svelte";
|
||||
|
||||
/* Const Init */
|
||||
const ccconfig = getContext("cc-config");
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!--
|
||||
@component User settings wrapper
|
||||
@component User settings wrapper
|
||||
|
||||
Properties:
|
||||
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
|
||||
- `isApi Bool!`: Is currently logged in user api authority
|
||||
-->
|
||||
Properties:
|
||||
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
|
||||
- `isApi Bool!`: Is currently logged in user api authority
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<!--
|
||||
@component User creation form card
|
||||
@component User creation form card
|
||||
|
||||
Properties:
|
||||
- `roles [String]!`: List of roles used in app as strings
|
||||
Properties:
|
||||
- `roles [String]!`: List of roles used in app as strings
|
||||
- `reloadUser Func`: The callback function to reload the user list
|
||||
-->
|
||||
|
||||
Events:
|
||||
- `reload`: Trigger upstream reload of user list after user creation
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Button, Card, CardTitle } from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { roles, reloadUser } = $props();
|
||||
let {
|
||||
roles,
|
||||
reloadUser
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let message = $state({ msg: "", color: "#d63384" });
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<!--
|
||||
@component User managed project edit form card
|
||||
@component User managed project edit form card
|
||||
|
||||
Events:
|
||||
- `reload`: Trigger upstream reload of user list after project update
|
||||
-->
|
||||
Properties:
|
||||
- `reloadUser Func`: The callback function to reload the user list
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Card, CardTitle, CardBody } from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { reloadUser } = $props();
|
||||
let {
|
||||
reloadUser
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let message = $state({ msg: "", color: "#d63384" });
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<!--
|
||||
@component User role edit form card
|
||||
@component User role edit form card
|
||||
|
||||
Properties:
|
||||
- `roles [String]!`: List of roles used in app as strings
|
||||
|
||||
Events:
|
||||
- `reload`: Trigger upstream reload of user list after role edit
|
||||
-->
|
||||
Properties:
|
||||
- `roles [String]!`: List of roles used in app as strings
|
||||
- `reloadUser Func`: The callback function to reload the user list
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Card, CardTitle, CardBody } from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* SVelte 5 Props */
|
||||
let {roles, reloadUser } = $props();
|
||||
let {
|
||||
roles,
|
||||
reloadUser
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let message = $state({ msg: "", color: "#d63384" });
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<!--
|
||||
@component Admin edit notice.txt content card
|
||||
-->
|
||||
@component Admin edit notice content card
|
||||
|
||||
Properties:
|
||||
- `ncontent String`: The homepage notice content
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Col, Card, CardTitle, CardBody } from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { ncontent } = $props();
|
||||
let {
|
||||
ncontent
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let message = $state({ msg: "", color: "#d63384" });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!--
|
||||
@component Admin option select card
|
||||
-->
|
||||
@component Admin option select card
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext, onMount } from "svelte";
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<!--
|
||||
@component User management table
|
||||
@component User management table
|
||||
|
||||
Properties:
|
||||
- `users [Object]?`: List of users
|
||||
|
||||
Events:
|
||||
- `reload`: Trigger upstream reload of user list
|
||||
-->
|
||||
Properties:
|
||||
- `users [Object]?`: List of users [Bindable, Default: []]
|
||||
- `reloadUser Func`: The callback function to reload the user list
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
@@ -19,7 +17,10 @@
|
||||
import ShowUsersRow from "./ShowUsersRow.svelte";
|
||||
|
||||
/*Svelte 5 Props */
|
||||
let { users = $bindable([]), reloadUser } = $props();
|
||||
let {
|
||||
users = $bindable([]),
|
||||
reloadUser
|
||||
} = $props();
|
||||
|
||||
/* Functions */
|
||||
function deleteUser(username) {
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<!--
|
||||
@component User data row for table
|
||||
@component User data row for table
|
||||
|
||||
Properties:
|
||||
- `user Object!`: User Object
|
||||
- {username: String, name: String, roles: [String], projects: String, email: String}
|
||||
-->
|
||||
Properties:
|
||||
- `user Object!`: User Object
|
||||
- {username: String, name: String, roles: [String], projects: String, email: String}
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Button } from "@sveltestrap/sveltestrap";
|
||||
import { fetchJwt } from "../../generic/utils.js"
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { user } = $props();
|
||||
let {
|
||||
user
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let jwt = $state("");
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<!--
|
||||
@component Support option select card
|
||||
-->
|
||||
@component Support option select card
|
||||
|
||||
Properties:
|
||||
- `config Object`: Config includes latest option states
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Row, Col, Card, CardTitle, CardBody, Button} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { config } = $props();
|
||||
let {
|
||||
config
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let message = $state("");
|
||||
|
||||
@@ -1,394 +1,392 @@
|
||||
<!--
|
||||
@component Plot color scheme selection for users
|
||||
@component Plot color scheme selection for users
|
||||
|
||||
Properties:
|
||||
- `config Object`: Current cc-config
|
||||
- `message Object`: Message to display on success or error
|
||||
- `displayMessage Bool`: If to display message content
|
||||
|
||||
Events:
|
||||
- `update-config, {selector: String, target: String}`: Trigger upstream update of the config option
|
||||
-->
|
||||
Properties:
|
||||
- `config Object`: Current cc-config
|
||||
- `message Object`: Message to display on success or error [Bindable]
|
||||
- `displayMessage Bool`: If to display message content [Bindable]
|
||||
- `cbmode Bool?`: Current colorblindness mode state [Bindable, Default: false]
|
||||
- `updateSetting Func`: The callback function to apply current option selection
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
Table,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
CardTitle,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
import {
|
||||
Table,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
CardTitle,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
config,
|
||||
message = $bindable(),
|
||||
displayMessage = $bindable(),
|
||||
cbmode = $bindable(false),
|
||||
updateSetting
|
||||
} = $props();
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
config,
|
||||
message = $bindable(),
|
||||
displayMessage = $bindable(),
|
||||
cbmode = $bindable(false),
|
||||
updateSetting
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let activeRow = $state(JSON.stringify(config?.plot_general_colorscheme));
|
||||
/* State Init */
|
||||
let activeRow = $state(JSON.stringify(config?.plot_general_colorscheme));
|
||||
|
||||
/* Const Init */
|
||||
const colorschemes = {
|
||||
Default: [
|
||||
"#00bfff",
|
||||
"#0000ff",
|
||||
"#ff00ff",
|
||||
"#ff0000",
|
||||
"#ff8000",
|
||||
"#ffff00",
|
||||
"#80ff00",
|
||||
],
|
||||
Autumn: [
|
||||
"rgb(255,0,0)",
|
||||
"rgb(255,11,0)",
|
||||
"rgb(255,20,0)",
|
||||
"rgb(255,30,0)",
|
||||
"rgb(255,41,0)",
|
||||
"rgb(255,50,0)",
|
||||
"rgb(255,60,0)",
|
||||
"rgb(255,71,0)",
|
||||
"rgb(255,80,0)",
|
||||
"rgb(255,90,0)",
|
||||
"rgb(255,101,0)",
|
||||
"rgb(255,111,0)",
|
||||
"rgb(255,120,0)",
|
||||
"rgb(255,131,0)",
|
||||
"rgb(255,141,0)",
|
||||
"rgb(255,150,0)",
|
||||
"rgb(255,161,0)",
|
||||
"rgb(255,171,0)",
|
||||
"rgb(255,180,0)",
|
||||
"rgb(255,190,0)",
|
||||
"rgb(255,201,0)",
|
||||
"rgb(255,210,0)",
|
||||
"rgb(255,220,0)",
|
||||
"rgb(255,231,0)",
|
||||
"rgb(255,240,0)",
|
||||
"rgb(255,250,0)",
|
||||
],
|
||||
Beach: [
|
||||
"rgb(0,252,0)",
|
||||
"rgb(0,233,0)",
|
||||
"rgb(0,212,0)",
|
||||
"rgb(0,189,0)",
|
||||
"rgb(0,169,0)",
|
||||
"rgb(0,148,0)",
|
||||
"rgb(0,129,4)",
|
||||
"rgb(0,145,46)",
|
||||
"rgb(0,162,90)",
|
||||
"rgb(0,180,132)",
|
||||
"rgb(29,143,136)",
|
||||
"rgb(73,88,136)",
|
||||
"rgb(115,32,136)",
|
||||
"rgb(81,9,64)",
|
||||
"rgb(124,51,23)",
|
||||
"rgb(162,90,0)",
|
||||
"rgb(194,132,0)",
|
||||
"rgb(220,171,0)",
|
||||
"rgb(231,213,0)",
|
||||
"rgb(0,0,13)",
|
||||
"rgb(0,0,55)",
|
||||
"rgb(0,0,92)",
|
||||
"rgb(0,0,127)",
|
||||
"rgb(0,0,159)",
|
||||
"rgb(0,0,196)",
|
||||
"rgb(0,0,233)",
|
||||
],
|
||||
BlueRed: [
|
||||
"rgb(0,0,131)",
|
||||
"rgb(0,0,168)",
|
||||
"rgb(0,0,208)",
|
||||
"rgb(0,0,247)",
|
||||
"rgb(0,27,255)",
|
||||
"rgb(0,67,255)",
|
||||
"rgb(0,108,255)",
|
||||
"rgb(0,148,255)",
|
||||
"rgb(0,187,255)",
|
||||
"rgb(0,227,255)",
|
||||
"rgb(8,255,247)",
|
||||
"rgb(48,255,208)",
|
||||
"rgb(87,255,168)",
|
||||
"rgb(127,255,127)",
|
||||
"rgb(168,255,87)",
|
||||
"rgb(208,255,48)",
|
||||
"rgb(247,255,8)",
|
||||
"rgb(255,224,0)",
|
||||
"rgb(255,183,0)",
|
||||
"rgb(255,143,0)",
|
||||
"rgb(255,104,0)",
|
||||
"rgb(255,64,0)",
|
||||
"rgb(255,23,0)",
|
||||
"rgb(238,0,0)",
|
||||
"rgb(194,0,0)",
|
||||
"rgb(150,0,0)",
|
||||
],
|
||||
Rainbow: [
|
||||
"rgb(125,0,255)",
|
||||
"rgb(85,0,255)",
|
||||
"rgb(39,0,255)",
|
||||
"rgb(0,6,255)",
|
||||
"rgb(0,51,255)",
|
||||
"rgb(0,97,255)",
|
||||
"rgb(0,141,255)",
|
||||
"rgb(0,187,255)",
|
||||
"rgb(0,231,255)",
|
||||
"rgb(0,255,233)",
|
||||
"rgb(0,255,189)",
|
||||
"rgb(0,255,143)",
|
||||
"rgb(0,255,99)",
|
||||
"rgb(0,255,53)",
|
||||
"rgb(0,255,9)",
|
||||
"rgb(37,255,0)",
|
||||
"rgb(83,255,0)",
|
||||
"rgb(127,255,0)",
|
||||
"rgb(173,255,0)",
|
||||
"rgb(217,255,0)",
|
||||
"rgb(255,248,0)",
|
||||
"rgb(255,203,0)",
|
||||
"rgb(255,159,0)",
|
||||
"rgb(255,113,0)",
|
||||
"rgb(255,69,0)",
|
||||
"rgb(255,23,0)",
|
||||
],
|
||||
Binary: [
|
||||
"rgb(215,215,215)",
|
||||
"rgb(206,206,206)",
|
||||
"rgb(196,196,196)",
|
||||
"rgb(185,185,185)",
|
||||
"rgb(176,176,176)",
|
||||
"rgb(166,166,166)",
|
||||
"rgb(155,155,155)",
|
||||
"rgb(145,145,145)",
|
||||
"rgb(136,136,136)",
|
||||
"rgb(125,125,125)",
|
||||
"rgb(115,115,115)",
|
||||
"rgb(106,106,106)",
|
||||
"rgb(95,95,95)",
|
||||
"rgb(85,85,85)",
|
||||
"rgb(76,76,76)",
|
||||
"rgb(66,66,66)",
|
||||
"rgb(55,55,55)",
|
||||
"rgb(46,46,46)",
|
||||
"rgb(36,36,36)",
|
||||
"rgb(25,25,25)",
|
||||
"rgb(16,16,16)",
|
||||
"rgb(6,6,6)",
|
||||
],
|
||||
GistEarth: [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(2,7,117)",
|
||||
"rgb(9,30,118)",
|
||||
"rgb(16,53,120)",
|
||||
"rgb(23,73,122)",
|
||||
"rgb(31,93,124)",
|
||||
"rgb(39,110,125)",
|
||||
"rgb(47,126,127)",
|
||||
"rgb(51,133,119)",
|
||||
"rgb(57,138,106)",
|
||||
"rgb(62,145,94)",
|
||||
"rgb(66,150,82)",
|
||||
"rgb(74,157,71)",
|
||||
"rgb(97,162,77)",
|
||||
"rgb(121,168,83)",
|
||||
"rgb(136,173,85)",
|
||||
"rgb(153,176,88)",
|
||||
"rgb(170,180,92)",
|
||||
"rgb(185,182,94)",
|
||||
"rgb(189,173,99)",
|
||||
"rgb(192,164,101)",
|
||||
"rgb(203,169,124)",
|
||||
"rgb(215,178,149)",
|
||||
"rgb(226,192,176)",
|
||||
"rgb(238,212,204)",
|
||||
"rgb(248,236,236)",
|
||||
],
|
||||
BlueWaves: [
|
||||
"rgb(83,0,215)",
|
||||
"rgb(43,6,108)",
|
||||
"rgb(9,16,16)",
|
||||
"rgb(8,32,25)",
|
||||
"rgb(0,50,8)",
|
||||
"rgb(27,64,66)",
|
||||
"rgb(69,67,178)",
|
||||
"rgb(115,62,210)",
|
||||
"rgb(155,50,104)",
|
||||
"rgb(178,43,41)",
|
||||
"rgb(180,51,34)",
|
||||
"rgb(161,78,87)",
|
||||
"rgb(124,117,187)",
|
||||
"rgb(78,155,203)",
|
||||
"rgb(34,178,85)",
|
||||
"rgb(4,176,2)",
|
||||
"rgb(9,152,27)",
|
||||
"rgb(4,118,2)",
|
||||
"rgb(34,92,85)",
|
||||
"rgb(78,92,203)",
|
||||
"rgb(124,127,187)",
|
||||
"rgb(161,187,87)",
|
||||
"rgb(180,248,34)",
|
||||
"rgb(178,220,41)",
|
||||
"rgb(155,217,104)",
|
||||
"rgb(115,254,210)",
|
||||
],
|
||||
BlueGreenRedYellow: [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(0,0,20)",
|
||||
"rgb(0,0,41)",
|
||||
"rgb(0,0,62)",
|
||||
"rgb(0,25,83)",
|
||||
"rgb(0,57,101)",
|
||||
"rgb(0,87,101)",
|
||||
"rgb(0,118,101)",
|
||||
"rgb(0,150,101)",
|
||||
"rgb(0,150,69)",
|
||||
"rgb(0,148,37)",
|
||||
"rgb(0,141,6)",
|
||||
"rgb(60,120,0)",
|
||||
"rgb(131,87,0)",
|
||||
"rgb(180,25,0)",
|
||||
"rgb(203,13,0)",
|
||||
"rgb(208,36,0)",
|
||||
"rgb(213,60,0)",
|
||||
"rgb(219,83,0)",
|
||||
"rgb(224,106,0)",
|
||||
"rgb(229,129,0)",
|
||||
"rgb(233,152,0)",
|
||||
"rgb(238,176,0)",
|
||||
"rgb(243,199,0)",
|
||||
"rgb(248,222,0)",
|
||||
"rgb(254,245,0)",
|
||||
],
|
||||
};
|
||||
/* Const Init */
|
||||
const colorschemes = {
|
||||
Default: [
|
||||
"#00bfff",
|
||||
"#0000ff",
|
||||
"#ff00ff",
|
||||
"#ff0000",
|
||||
"#ff8000",
|
||||
"#ffff00",
|
||||
"#80ff00",
|
||||
],
|
||||
Autumn: [
|
||||
"rgb(255,0,0)",
|
||||
"rgb(255,11,0)",
|
||||
"rgb(255,20,0)",
|
||||
"rgb(255,30,0)",
|
||||
"rgb(255,41,0)",
|
||||
"rgb(255,50,0)",
|
||||
"rgb(255,60,0)",
|
||||
"rgb(255,71,0)",
|
||||
"rgb(255,80,0)",
|
||||
"rgb(255,90,0)",
|
||||
"rgb(255,101,0)",
|
||||
"rgb(255,111,0)",
|
||||
"rgb(255,120,0)",
|
||||
"rgb(255,131,0)",
|
||||
"rgb(255,141,0)",
|
||||
"rgb(255,150,0)",
|
||||
"rgb(255,161,0)",
|
||||
"rgb(255,171,0)",
|
||||
"rgb(255,180,0)",
|
||||
"rgb(255,190,0)",
|
||||
"rgb(255,201,0)",
|
||||
"rgb(255,210,0)",
|
||||
"rgb(255,220,0)",
|
||||
"rgb(255,231,0)",
|
||||
"rgb(255,240,0)",
|
||||
"rgb(255,250,0)",
|
||||
],
|
||||
Beach: [
|
||||
"rgb(0,252,0)",
|
||||
"rgb(0,233,0)",
|
||||
"rgb(0,212,0)",
|
||||
"rgb(0,189,0)",
|
||||
"rgb(0,169,0)",
|
||||
"rgb(0,148,0)",
|
||||
"rgb(0,129,4)",
|
||||
"rgb(0,145,46)",
|
||||
"rgb(0,162,90)",
|
||||
"rgb(0,180,132)",
|
||||
"rgb(29,143,136)",
|
||||
"rgb(73,88,136)",
|
||||
"rgb(115,32,136)",
|
||||
"rgb(81,9,64)",
|
||||
"rgb(124,51,23)",
|
||||
"rgb(162,90,0)",
|
||||
"rgb(194,132,0)",
|
||||
"rgb(220,171,0)",
|
||||
"rgb(231,213,0)",
|
||||
"rgb(0,0,13)",
|
||||
"rgb(0,0,55)",
|
||||
"rgb(0,0,92)",
|
||||
"rgb(0,0,127)",
|
||||
"rgb(0,0,159)",
|
||||
"rgb(0,0,196)",
|
||||
"rgb(0,0,233)",
|
||||
],
|
||||
BlueRed: [
|
||||
"rgb(0,0,131)",
|
||||
"rgb(0,0,168)",
|
||||
"rgb(0,0,208)",
|
||||
"rgb(0,0,247)",
|
||||
"rgb(0,27,255)",
|
||||
"rgb(0,67,255)",
|
||||
"rgb(0,108,255)",
|
||||
"rgb(0,148,255)",
|
||||
"rgb(0,187,255)",
|
||||
"rgb(0,227,255)",
|
||||
"rgb(8,255,247)",
|
||||
"rgb(48,255,208)",
|
||||
"rgb(87,255,168)",
|
||||
"rgb(127,255,127)",
|
||||
"rgb(168,255,87)",
|
||||
"rgb(208,255,48)",
|
||||
"rgb(247,255,8)",
|
||||
"rgb(255,224,0)",
|
||||
"rgb(255,183,0)",
|
||||
"rgb(255,143,0)",
|
||||
"rgb(255,104,0)",
|
||||
"rgb(255,64,0)",
|
||||
"rgb(255,23,0)",
|
||||
"rgb(238,0,0)",
|
||||
"rgb(194,0,0)",
|
||||
"rgb(150,0,0)",
|
||||
],
|
||||
Rainbow: [
|
||||
"rgb(125,0,255)",
|
||||
"rgb(85,0,255)",
|
||||
"rgb(39,0,255)",
|
||||
"rgb(0,6,255)",
|
||||
"rgb(0,51,255)",
|
||||
"rgb(0,97,255)",
|
||||
"rgb(0,141,255)",
|
||||
"rgb(0,187,255)",
|
||||
"rgb(0,231,255)",
|
||||
"rgb(0,255,233)",
|
||||
"rgb(0,255,189)",
|
||||
"rgb(0,255,143)",
|
||||
"rgb(0,255,99)",
|
||||
"rgb(0,255,53)",
|
||||
"rgb(0,255,9)",
|
||||
"rgb(37,255,0)",
|
||||
"rgb(83,255,0)",
|
||||
"rgb(127,255,0)",
|
||||
"rgb(173,255,0)",
|
||||
"rgb(217,255,0)",
|
||||
"rgb(255,248,0)",
|
||||
"rgb(255,203,0)",
|
||||
"rgb(255,159,0)",
|
||||
"rgb(255,113,0)",
|
||||
"rgb(255,69,0)",
|
||||
"rgb(255,23,0)",
|
||||
],
|
||||
Binary: [
|
||||
"rgb(215,215,215)",
|
||||
"rgb(206,206,206)",
|
||||
"rgb(196,196,196)",
|
||||
"rgb(185,185,185)",
|
||||
"rgb(176,176,176)",
|
||||
"rgb(166,166,166)",
|
||||
"rgb(155,155,155)",
|
||||
"rgb(145,145,145)",
|
||||
"rgb(136,136,136)",
|
||||
"rgb(125,125,125)",
|
||||
"rgb(115,115,115)",
|
||||
"rgb(106,106,106)",
|
||||
"rgb(95,95,95)",
|
||||
"rgb(85,85,85)",
|
||||
"rgb(76,76,76)",
|
||||
"rgb(66,66,66)",
|
||||
"rgb(55,55,55)",
|
||||
"rgb(46,46,46)",
|
||||
"rgb(36,36,36)",
|
||||
"rgb(25,25,25)",
|
||||
"rgb(16,16,16)",
|
||||
"rgb(6,6,6)",
|
||||
],
|
||||
GistEarth: [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(2,7,117)",
|
||||
"rgb(9,30,118)",
|
||||
"rgb(16,53,120)",
|
||||
"rgb(23,73,122)",
|
||||
"rgb(31,93,124)",
|
||||
"rgb(39,110,125)",
|
||||
"rgb(47,126,127)",
|
||||
"rgb(51,133,119)",
|
||||
"rgb(57,138,106)",
|
||||
"rgb(62,145,94)",
|
||||
"rgb(66,150,82)",
|
||||
"rgb(74,157,71)",
|
||||
"rgb(97,162,77)",
|
||||
"rgb(121,168,83)",
|
||||
"rgb(136,173,85)",
|
||||
"rgb(153,176,88)",
|
||||
"rgb(170,180,92)",
|
||||
"rgb(185,182,94)",
|
||||
"rgb(189,173,99)",
|
||||
"rgb(192,164,101)",
|
||||
"rgb(203,169,124)",
|
||||
"rgb(215,178,149)",
|
||||
"rgb(226,192,176)",
|
||||
"rgb(238,212,204)",
|
||||
"rgb(248,236,236)",
|
||||
],
|
||||
BlueWaves: [
|
||||
"rgb(83,0,215)",
|
||||
"rgb(43,6,108)",
|
||||
"rgb(9,16,16)",
|
||||
"rgb(8,32,25)",
|
||||
"rgb(0,50,8)",
|
||||
"rgb(27,64,66)",
|
||||
"rgb(69,67,178)",
|
||||
"rgb(115,62,210)",
|
||||
"rgb(155,50,104)",
|
||||
"rgb(178,43,41)",
|
||||
"rgb(180,51,34)",
|
||||
"rgb(161,78,87)",
|
||||
"rgb(124,117,187)",
|
||||
"rgb(78,155,203)",
|
||||
"rgb(34,178,85)",
|
||||
"rgb(4,176,2)",
|
||||
"rgb(9,152,27)",
|
||||
"rgb(4,118,2)",
|
||||
"rgb(34,92,85)",
|
||||
"rgb(78,92,203)",
|
||||
"rgb(124,127,187)",
|
||||
"rgb(161,187,87)",
|
||||
"rgb(180,248,34)",
|
||||
"rgb(178,220,41)",
|
||||
"rgb(155,217,104)",
|
||||
"rgb(115,254,210)",
|
||||
],
|
||||
BlueGreenRedYellow: [
|
||||
"rgb(0,0,0)",
|
||||
"rgb(0,0,20)",
|
||||
"rgb(0,0,41)",
|
||||
"rgb(0,0,62)",
|
||||
"rgb(0,25,83)",
|
||||
"rgb(0,57,101)",
|
||||
"rgb(0,87,101)",
|
||||
"rgb(0,118,101)",
|
||||
"rgb(0,150,101)",
|
||||
"rgb(0,150,69)",
|
||||
"rgb(0,148,37)",
|
||||
"rgb(0,141,6)",
|
||||
"rgb(60,120,0)",
|
||||
"rgb(131,87,0)",
|
||||
"rgb(180,25,0)",
|
||||
"rgb(203,13,0)",
|
||||
"rgb(208,36,0)",
|
||||
"rgb(213,60,0)",
|
||||
"rgb(219,83,0)",
|
||||
"rgb(224,106,0)",
|
||||
"rgb(229,129,0)",
|
||||
"rgb(233,152,0)",
|
||||
"rgb(238,176,0)",
|
||||
"rgb(243,199,0)",
|
||||
"rgb(248,222,0)",
|
||||
"rgb(254,245,0)",
|
||||
],
|
||||
};
|
||||
|
||||
// https://personal.sron.nl/~pault/
|
||||
// https://tsitsul.in/blog/coloropt/
|
||||
const cvdschemes = {
|
||||
HighContrast: [
|
||||
"rgb(221,170,51)",
|
||||
"rgb(187,85,102)",
|
||||
"rgb(0,68,136)",
|
||||
"rgb(0,0,0)",
|
||||
],
|
||||
Bright: [
|
||||
"rgb(68,119,170)",
|
||||
"rgb(102,204,238)",
|
||||
"rgb(34,136,51)",
|
||||
"rgb(204,187,68)",
|
||||
"rgb(238,102,119)",
|
||||
"rgb(170,51,119)",
|
||||
"rgb(187,187,187)",
|
||||
],
|
||||
Muted: [
|
||||
"rgb(51,34,136)",
|
||||
"rgb(136,204,238)",
|
||||
"rgb(68,170,153)",
|
||||
"rgb(17,119,51)",
|
||||
"rgb(153,153,51)",
|
||||
"rgb(221,204,119)",
|
||||
"rgb(204,102,119)",
|
||||
"rgb(136,34,85)",
|
||||
"rgb(170,68,153)",
|
||||
"rgb(221,221,221)",
|
||||
],
|
||||
NormalSixColor: [
|
||||
"rgb(64,83,211)",
|
||||
"rgb(221,179,16)",
|
||||
"rgb(181,29,20)",
|
||||
"rgb(0,190,255)",
|
||||
"rgb(251,73,176)",
|
||||
"rgb(0,178,93)",
|
||||
"rgb(202,202,202)",
|
||||
],
|
||||
NormalTwelveColor: [
|
||||
"rgb(235,172,35)",
|
||||
"rgb(184,0,88)",
|
||||
"rgb(0,140,249)",
|
||||
"rgb(0,110,0)",
|
||||
"rgb(0,187,173)",
|
||||
"rgb(209,99,230)",
|
||||
"rgb(178,69,2)",
|
||||
"rgb(255,146,135)",
|
||||
"rgb(89,84,214)",
|
||||
"rgb(0,198,248)",
|
||||
"rgb(135,133,0)",
|
||||
"rgb(0,167,108)",
|
||||
"rgb(189,189,189)",
|
||||
]
|
||||
}
|
||||
// https://personal.sron.nl/~pault/
|
||||
// https://tsitsul.in/blog/coloropt/
|
||||
const cvdschemes = {
|
||||
HighContrast: [
|
||||
"rgb(221,170,51)",
|
||||
"rgb(187,85,102)",
|
||||
"rgb(0,68,136)",
|
||||
"rgb(0,0,0)",
|
||||
],
|
||||
Bright: [
|
||||
"rgb(68,119,170)",
|
||||
"rgb(102,204,238)",
|
||||
"rgb(34,136,51)",
|
||||
"rgb(204,187,68)",
|
||||
"rgb(238,102,119)",
|
||||
"rgb(170,51,119)",
|
||||
"rgb(187,187,187)",
|
||||
],
|
||||
Muted: [
|
||||
"rgb(51,34,136)",
|
||||
"rgb(136,204,238)",
|
||||
"rgb(68,170,153)",
|
||||
"rgb(17,119,51)",
|
||||
"rgb(153,153,51)",
|
||||
"rgb(221,204,119)",
|
||||
"rgb(204,102,119)",
|
||||
"rgb(136,34,85)",
|
||||
"rgb(170,68,153)",
|
||||
"rgb(221,221,221)",
|
||||
],
|
||||
NormalSixColor: [
|
||||
"rgb(64,83,211)",
|
||||
"rgb(221,179,16)",
|
||||
"rgb(181,29,20)",
|
||||
"rgb(0,190,255)",
|
||||
"rgb(251,73,176)",
|
||||
"rgb(0,178,93)",
|
||||
"rgb(202,202,202)",
|
||||
],
|
||||
NormalTwelveColor: [
|
||||
"rgb(235,172,35)",
|
||||
"rgb(184,0,88)",
|
||||
"rgb(0,140,249)",
|
||||
"rgb(0,110,0)",
|
||||
"rgb(0,187,173)",
|
||||
"rgb(209,99,230)",
|
||||
"rgb(178,69,2)",
|
||||
"rgb(255,146,135)",
|
||||
"rgb(89,84,214)",
|
||||
"rgb(0,198,248)",
|
||||
"rgb(135,133,0)",
|
||||
"rgb(0,167,108)",
|
||||
"rgb(189,189,189)",
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<Row cols={1} class="p-2 g-2">
|
||||
<!-- COLORSCHEME -->
|
||||
<Col
|
||||
><Card>
|
||||
<form
|
||||
id="colorscheme-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
<!-- COLORSCHEME -->
|
||||
<Col>
|
||||
<Card>
|
||||
<form
|
||||
id="colorscheme-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Color Scheme for Timeseries Plots {cbmode ? `(Color Blind Friendly Palettes)` : ``}</div>
|
||||
{#if displayMessage && message.target == "cs"}<div
|
||||
style="margin-left: auto; font-size: 0.9em;"
|
||||
>
|
||||
<code style="color: {message.color};" out:fade
|
||||
>Update: {message.msg}</code
|
||||
>
|
||||
</div>{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_colorscheme" />
|
||||
<Table hover>
|
||||
<tbody>
|
||||
{#key activeRow}
|
||||
{#each Object.entries(cbmode ? cvdschemes : colorschemes) as [name, rgbrow]}
|
||||
<tr>
|
||||
<th scope="col">{name}</th>
|
||||
<td>
|
||||
<input
|
||||
type="radio"
|
||||
name="value"
|
||||
value={JSON.stringify(rgbrow)}
|
||||
checked={activeRow == JSON.stringify(rgbrow)}
|
||||
onclick={(e) => {
|
||||
activeRow = JSON.stringify(rgbrow)
|
||||
updateSetting(e, {
|
||||
selector: "#colorscheme-form",
|
||||
target: "cs",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{#each rgbrow as rgb}
|
||||
<span class="color-dot" style="background-color: {rgb};"
|
||||
></span>
|
||||
{/each}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/key}
|
||||
</tbody>
|
||||
</Table>
|
||||
</form>
|
||||
</Card></Col
|
||||
>
|
||||
<div>Color Scheme for Timeseries Plots {cbmode ? `(Color Blind Friendly Palettes)` : ``}</div>
|
||||
{#if displayMessage && message.target == "cs"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_colorscheme" />
|
||||
<Table hover>
|
||||
<tbody>
|
||||
{#key activeRow}
|
||||
{#each Object.entries(cbmode ? cvdschemes : colorschemes) as [name, rgbrow]}
|
||||
<tr>
|
||||
<th scope="col">{name}</th>
|
||||
<td>
|
||||
<input
|
||||
type="radio"
|
||||
name="value"
|
||||
value={JSON.stringify(rgbrow)}
|
||||
checked={activeRow == JSON.stringify(rgbrow)}
|
||||
onclick={(e) => {
|
||||
activeRow = JSON.stringify(rgbrow)
|
||||
updateSetting(e, {
|
||||
selector: "#colorscheme-form",
|
||||
target: "cs",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{#each rgbrow as rgb}
|
||||
<span class="color-dot" style="background-color: {rgb};"></span>
|
||||
{/each}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/key}
|
||||
</tbody>
|
||||
</Table>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<style>
|
||||
.color-dot {
|
||||
.color-dot {
|
||||
margin-left: 1px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,224 +1,222 @@
|
||||
<!--
|
||||
@component Plot render option selection for users
|
||||
@component Plot render option selection for users
|
||||
|
||||
Properties:
|
||||
- `config Object`: Current cc-config
|
||||
- `message Object`: Message to display on success or error
|
||||
- `displayMessage Bool`: If to display message content
|
||||
Properties:
|
||||
- `config Object`: Current cc-config
|
||||
- `message Object`: Message to display on success or error [Bindable]
|
||||
- `displayMessage Bool`: If to display message content [Bindable]
|
||||
- `updateSetting Func`: The callback function to apply current option selection
|
||||
-->
|
||||
|
||||
Events:
|
||||
- `update-config, {selector: String, target: String}`: Trigger upstream update of the config option
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
CardTitle,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
<script>
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
CardTitle,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
config,
|
||||
message = $bindable(),
|
||||
displayMessage = $bindable(),
|
||||
updateSetting
|
||||
} = $props();
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
config,
|
||||
message = $bindable(),
|
||||
displayMessage = $bindable(),
|
||||
updateSetting
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Row cols={3} class="p-2 g-2">
|
||||
<!-- LINE WIDTH -->
|
||||
<Col
|
||||
><Card class="h-100">
|
||||
<form
|
||||
id="line-width-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#line-width-form",
|
||||
target: "lw",
|
||||
})}
|
||||
<!-- LINE WIDTH -->
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<form
|
||||
id="line-width-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#line-width-form",
|
||||
target: "lw",
|
||||
})}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Line Width</div>
|
||||
<!-- Expand If-Clause for clarity once -->
|
||||
{#if displayMessage && message.target == "lw"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
<div>Line Width</div>
|
||||
<!-- Expand If-Clause for clarity once -->
|
||||
{#if displayMessage && message.target == "lw"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_lineWidth" />
|
||||
<div class="mb-3">
|
||||
<label for="value" class="form-label">Line Width</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="lwvalue"
|
||||
name="value"
|
||||
aria-describedby="lineWidthHelp"
|
||||
value={config.plot_general_lineWidth}
|
||||
min="1"
|
||||
/>
|
||||
<div id="lineWidthHelp" class="form-text">
|
||||
Width of the lines in the timeseries plots.
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<!-- PLOTS PER ROW -->
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<form
|
||||
id="plots-per-row-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#plots-per-row-form",
|
||||
target: "ppr",
|
||||
})}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Plots per Row</div>
|
||||
{#if displayMessage && message.target == "ppr"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_view_plotsPerRow" />
|
||||
<div class="mb-3">
|
||||
<label for="value" class="form-label">Plots per Row</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="pprvalue"
|
||||
name="value"
|
||||
aria-describedby="plotsperrowHelp"
|
||||
value={config.plot_view_plotsPerRow}
|
||||
min="1"
|
||||
/>
|
||||
<div id="plotsperrowHelp" class="form-text">
|
||||
How many plots to show next to each other on pages such as
|
||||
/monitoring/job/, /monitoring/system/...
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<!-- BACKGROUND -->
|
||||
<Col class="d-flex justify-content-between">
|
||||
<Card class="h-100" style="width: 49%;">
|
||||
<form
|
||||
id="backgrounds-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#backgrounds-form",
|
||||
target: "bg",
|
||||
})}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Colored Backgrounds</div>
|
||||
{#if displayMessage && message.target == "bg"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_colorBackground" />
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
{#if config.plot_general_colorBackground}
|
||||
<input type="radio" id="colb-true-checked" name="value" value="true" checked />
|
||||
{:else}
|
||||
<input type="radio" id="colb-true" name="value" value="true" />
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_lineWidth" />
|
||||
<div class="mb-3">
|
||||
<label for="value" class="form-label">Line Width</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="lwvalue"
|
||||
name="value"
|
||||
aria-describedby="lineWidthHelp"
|
||||
value={config.plot_general_lineWidth}
|
||||
min="1"
|
||||
/>
|
||||
<div id="lineWidthHelp" class="form-text">
|
||||
Width of the lines in the timeseries plots.
|
||||
</div>
|
||||
<label for="true">Yes</label>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card></Col
|
||||
>
|
||||
|
||||
<!-- PLOTS PER ROW -->
|
||||
<Col
|
||||
><Card class="h-100">
|
||||
<form
|
||||
id="plots-per-row-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#plots-per-row-form",
|
||||
target: "ppr",
|
||||
})}
|
||||
<div>
|
||||
{#if config.plot_general_colorBackground}
|
||||
<input type="radio" id="colb-false" name="value" value="false" />
|
||||
{:else}
|
||||
<input type="radio" id="colb-false-checked" name="value" value="false" checked />
|
||||
{/if}
|
||||
<label for="false">No</label>
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
<Card class="h-100" style="width: 49%;">
|
||||
<form
|
||||
id="colorblindmode-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#colorblindmode-form",
|
||||
target: "cbm",
|
||||
})}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Plots per Row</div>
|
||||
{#if displayMessage && message.target == "ppr"}<div
|
||||
style="margin-left: auto; font-size: 0.9em;"
|
||||
>
|
||||
<code style="color: {message.color};" out:fade
|
||||
>Update: {message.msg}</code
|
||||
>
|
||||
</div>{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_view_plotsPerRow" />
|
||||
<div class="mb-3">
|
||||
<label for="value" class="form-label">Plots per Row</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="pprvalue"
|
||||
name="value"
|
||||
aria-describedby="plotsperrowHelp"
|
||||
value={config.plot_view_plotsPerRow}
|
||||
min="1"
|
||||
/>
|
||||
<div id="plotsperrowHelp" class="form-text">
|
||||
How many plots to show next to each other on pages such as
|
||||
/monitoring/job/, /monitoring/system/...
|
||||
<div>Color Blind Mode</div>
|
||||
{#if displayMessage && message.target == "cbm"}
|
||||
<div style="margin-left: auto; font-size: 0.9em;">
|
||||
<code style="color: {message.color};" out:fade>
|
||||
Update: {message.msg}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_colorblindMode" />
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
{#if config?.plot_general_colorblindMode}
|
||||
<input type="radio" id="cbm-true-checked" name="value" value="true" checked />
|
||||
{:else}
|
||||
<input type="radio" id="cbm-true" name="value" value="true" />
|
||||
{/if}
|
||||
<label for="true">Yes</label>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card></Col
|
||||
>
|
||||
|
||||
<!-- BACKGROUND -->
|
||||
<Col class="d-flex justify-content-between"
|
||||
><Card class="h-100" style="width: 49%;">
|
||||
<form
|
||||
id="backgrounds-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#backgrounds-form",
|
||||
target: "bg",
|
||||
})}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Colored Backgrounds</div>
|
||||
{#if displayMessage && message.target == "bg"}<div
|
||||
style="margin-left: auto; font-size: 0.9em;"
|
||||
>
|
||||
<code style="color: {message.color};" out:fade
|
||||
>Update: {message.msg}</code
|
||||
>
|
||||
</div>{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_colorBackground" />
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
{#if config.plot_general_colorBackground}
|
||||
<input type="radio" id="colb-true-checked" name="value" value="true" checked />
|
||||
{:else}
|
||||
<input type="radio" id="colb-true" name="value" value="true" />
|
||||
{/if}
|
||||
<label for="true">Yes</label>
|
||||
</div>
|
||||
<div>
|
||||
{#if config.plot_general_colorBackground}
|
||||
<input type="radio" id="colb-false" name="value" value="false" />
|
||||
{:else}
|
||||
<input type="radio" id="colb-false-checked" name="value" value="false" checked />
|
||||
{/if}
|
||||
<label for="false">No</label>
|
||||
</div>
|
||||
<div>
|
||||
{#if config?.plot_general_colorblindMode}
|
||||
<input type="radio" id="cbm-false" name="value" value="false" />
|
||||
{:else}
|
||||
<input type="radio" id="cbm-false-checked" name="value" value="false" checked />
|
||||
{/if}
|
||||
<label for="false">No</label>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
<Card class="h-100" style="width: 49%;">
|
||||
<form
|
||||
id="colorblindmode-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
onsubmit={(e) => updateSetting(e, {
|
||||
selector: "#colorblindmode-form",
|
||||
target: "cbm",
|
||||
})}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Color Blind Mode</div>
|
||||
{#if displayMessage && message.target == "cbm"}<div
|
||||
style="margin-left: auto; font-size: 0.9em;"
|
||||
>
|
||||
<code style="color: {message.color};" out:fade
|
||||
>Update: {message.msg}</code
|
||||
>
|
||||
</div>{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="plot_general_colorblindMode" />
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
{#if config?.plot_general_colorblindMode}
|
||||
<input type="radio" id="cbm-true-checked" name="value" value="true" checked />
|
||||
{:else}
|
||||
<input type="radio" id="cbm-true" name="value" value="true" />
|
||||
{/if}
|
||||
<label for="true">Yes</label>
|
||||
</div>
|
||||
<div>
|
||||
{#if config?.plot_general_colorblindMode}
|
||||
<input type="radio" id="cbm-false" name="value" value="false" />
|
||||
{:else}
|
||||
<input type="radio" id="cbm-false-checked" name="value" value="false" checked />
|
||||
{/if}
|
||||
<label for="false">No</label>
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -1,82 +1,80 @@
|
||||
<!--
|
||||
@component General option selection for users
|
||||
@component General option selection for users
|
||||
|
||||
Properties:
|
||||
- `config Object`: Current cc-config
|
||||
- `message Object`: Message to display on success or error
|
||||
- `displayMessage Bool`: If to display message content
|
||||
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
|
||||
- `isApi Bool!`: Is currently logged in user api authority
|
||||
Properties:
|
||||
- `config Object`: Current cc-config
|
||||
- `message Object`: Message to display on success or error [Bindable]
|
||||
- `displayMessage Bool`: If to display message content [Bindable]
|
||||
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
|
||||
- `isApi Bool!`: Is currently logged in user api authority
|
||||
- `updateSetting Func`: The callback function to apply current option selection
|
||||
-->
|
||||
|
||||
Events:
|
||||
- `update-config, {selector: String, target: String}`: Trigger upstream update of the config option
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
CardTitle,
|
||||
CardBody
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
import { fetchJwt } from "../../generic/utils.js";
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
CardTitle,
|
||||
CardBody
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
import { fetchJwt } from "../../generic/utils.js";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
config,
|
||||
message = $bindable(),
|
||||
displayMessage = $bindable(),
|
||||
username,
|
||||
isApi,
|
||||
updateSetting
|
||||
} = $props();
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
config,
|
||||
message = $bindable(),
|
||||
displayMessage = $bindable(),
|
||||
username,
|
||||
isApi,
|
||||
updateSetting
|
||||
} = $props();
|
||||
|
||||
/* State Init */
|
||||
let jwt = $state("");
|
||||
let displayCheck = $state(false);
|
||||
/* State Init */
|
||||
let jwt = $state("");
|
||||
let displayCheck = $state(false);
|
||||
|
||||
/* Functions */
|
||||
function getUserJwt(username) {
|
||||
if (username) {
|
||||
const p = fetchJwt(username);
|
||||
p.then((content) => {
|
||||
jwt = content
|
||||
}).catch((error) => {
|
||||
console.error(`Could not get JWT: ${error}`);
|
||||
});
|
||||
}
|
||||
/* Functions */
|
||||
function getUserJwt(username) {
|
||||
if (username) {
|
||||
const p = fetchJwt(username);
|
||||
p.then((content) => {
|
||||
jwt = content
|
||||
}).catch((error) => {
|
||||
console.error(`Could not get JWT: ${error}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clipJwt() {
|
||||
displayCheck = true;
|
||||
// Navigator clipboard api needs a secure context (https)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard
|
||||
.writeText(jwt)
|
||||
.catch((reason) => console.error(reason));
|
||||
} else {
|
||||
// Workaround: Create, Fill, And Copy Content of Textarea
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = jwt;
|
||||
textArea.style.position = "absolute";
|
||||
textArea.style.left = "-999999px";
|
||||
document.body.prepend(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
textArea.remove();
|
||||
}
|
||||
function clipJwt() {
|
||||
displayCheck = true;
|
||||
// Navigator clipboard api needs a secure context (https)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard
|
||||
.writeText(jwt)
|
||||
.catch((reason) => console.error(reason));
|
||||
} else {
|
||||
// Workaround: Create, Fill, And Copy Content of Textarea
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = jwt;
|
||||
textArea.style.position = "absolute";
|
||||
textArea.style.left = "-999999px";
|
||||
document.body.prepend(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
textArea.remove();
|
||||
}
|
||||
setTimeout(function () {
|
||||
displayCheck = false;
|
||||
}, 1000);
|
||||
}
|
||||
setTimeout(function () {
|
||||
displayCheck = false;
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Row cols={isApi ? 3 : 1} class="p-2 g-2">
|
||||
@@ -133,41 +131,41 @@
|
||||
{#if isApi}
|
||||
<!-- USER-JWT BTN -->
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<CardBody>
|
||||
<CardTitle>Generate JWT</CardTitle>
|
||||
{#if jwt}
|
||||
<Button color="secondary" onclick={() => clipJwt()}>
|
||||
Copy JWT to Clipboard
|
||||
</Button>
|
||||
<p class="mt-2">
|
||||
Your token is displayed on the right. Press this button to copy it to the clipboard.
|
||||
</p>
|
||||
{#if displayCheck}
|
||||
<p class="mt-2">
|
||||
<span class="text-success">Copied!</span>
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<Button color="success" onclick={() => getUserJwt(username)}>
|
||||
Generate JWT for '{username}'
|
||||
</Button>
|
||||
<p class="mt-2">
|
||||
Generate a JSON Web Token for use with the ClusterCockpit REST-API endpoints.
|
||||
</p>
|
||||
{/if}
|
||||
</CardBody>
|
||||
</Card>
|
||||
<Card class="h-100">
|
||||
<CardBody>
|
||||
<CardTitle>Generate JWT</CardTitle>
|
||||
{#if jwt}
|
||||
<Button color="secondary" onclick={() => clipJwt()}>
|
||||
Copy JWT to Clipboard
|
||||
</Button>
|
||||
<p class="mt-2">
|
||||
Your token is displayed on the right. Press this button to copy it to the clipboard.
|
||||
</p>
|
||||
{#if displayCheck}
|
||||
<p class="mt-2">
|
||||
<span class="text-success">Copied!</span>
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<Button color="success" onclick={() => getUserJwt(username)}>
|
||||
Generate JWT for '{username}'
|
||||
</Button>
|
||||
<p class="mt-2">
|
||||
Generate a JSON Web Token for use with the ClusterCockpit REST-API endpoints.
|
||||
</p>
|
||||
{/if}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<!-- USER-JWT RES -->
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<CardBody>
|
||||
<CardTitle>Display JWT</CardTitle>
|
||||
<textarea cols="32" rows="5" readonly>{jwt ? jwt : 'Press "Gen. JWT" to request token ...'}</textarea>
|
||||
</CardBody>
|
||||
</Card>
|
||||
<Card class="h-100">
|
||||
<CardBody>
|
||||
<CardTitle>Display JWT</CardTitle>
|
||||
<textarea cols="32" rows="5" readonly>{jwt ? jwt : 'Press "Gen. JWT" to request token ...'}</textarea>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
{/if}
|
||||
</Row>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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/> :
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -6,7 +6,7 @@ const headerDomTarget = document.getElementById('svelte-header');
|
||||
if (headerDomTarget != null) {
|
||||
mount(Header, {
|
||||
target: headerDomTarget,
|
||||
props: { // { ...header },
|
||||
props: {
|
||||
username: hUsername,
|
||||
authlevel: hAuthlevel,
|
||||
clusters: hClusters,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!--
|
||||
@component Navbar component; renders in app navigation links as received from upstream
|
||||
@component Navbar component; renders in app navigation links as received from upstream
|
||||
|
||||
Properties:
|
||||
- `clusters [String]`: List of cluster names
|
||||
- `subClusters map[String][]string`: Map of subclusters by cluster names
|
||||
- `links [Object]`: Pre-filtered link objects based on user auth
|
||||
- `direction String?`: The direcion of the drop-down menue [default: down]
|
||||
-->
|
||||
Properties:
|
||||
- `clusters [String]`: List of cluster names
|
||||
- `subClusters map[String][]string`: Map of subclusters by cluster names
|
||||
- `links [Object]`: Pre-filtered link objects based on user auth
|
||||
- `direction String?`: The direcion of the drop-down menue [default: down]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!--
|
||||
@component Navbar component; renders in app resource links and user dropdown
|
||||
@component Navbar component; renders in app resource links and user dropdown
|
||||
|
||||
Properties:
|
||||
- `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
|
||||
- `screenSize Number`: The current window size, will trigger different render variants
|
||||
-->
|
||||
Properties:
|
||||
- `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
|
||||
- `screenSize Number`: The current window size, will trigger different render variants
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
@component Energy Summary component; Displays job energy information.
|
||||
@component Energy Summary component; Displays job energy information.
|
||||
|
||||
Properties:
|
||||
- `jobId Number`: The job id
|
||||
- `jobEnergy Number?`: The total job energy [Default: null]
|
||||
- `jobEnergyFootprint [Object]?`: The partial job energy contributions [Default: null]
|
||||
-->
|
||||
Properties:
|
||||
- `jobId Number`: The job id
|
||||
- `jobEnergy Number?`: The total job energy [Default: null]
|
||||
- `jobEnergyFootprint [Object]?`: The partial job energy contributions [Default: null]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!--
|
||||
@component Job View Roofline component; Queries data for and renders roofline plot.
|
||||
@component Job View Roofline component; Queries data for and renders roofline plot.
|
||||
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
- `clusters Array`: The GQL clusters array
|
||||
-->
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
- `clusters Array`: The GQL clusters array
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
@component Job Summary component; Displays aggregated job footprint statistics and performance indicators
|
||||
@component Job Summary component; Displays aggregated job footprint statistics and performance indicators
|
||||
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
- `width String?`: Width of the card [Default: 'auto']
|
||||
- `height String?`: Height of the card [Default: '310px']
|
||||
-->
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
- `width String?`: Width of the card [Default: 'auto']
|
||||
- `height String?`: Height of the card [Default: '400px']
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
@@ -17,7 +17,6 @@
|
||||
import JobFootprintBars from "./jobsummary/JobFootprintBars.svelte";
|
||||
import JobFootprintPolar from "./jobsummary/JobFootprintPolar.svelte";
|
||||
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
job,
|
||||
@@ -25,6 +24,7 @@
|
||||
height = "400px",
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
const showFootprintTab = !!getContext("cc-config")[`job_view_showFootprint`];
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<!--
|
||||
@component Metric plot wrapper with user scope selection; used in job detail view
|
||||
@component Metric plot wrapper with user scope selection; used in job detail view
|
||||
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
- `metricName String`: The metrics name
|
||||
- `metricUnit Object`: The metrics GQL unit object
|
||||
- `nativeScope String`: The metrics native scope
|
||||
- `scopes [String]`: The scopes returned for this metric
|
||||
- `rawData [Object]`: Metric data for all scopes returned for this metric
|
||||
- `isShared Bool?`: If this job used shared resources; will adapt threshold indicators accordingly in downstream plots [Default: false]
|
||||
-->
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
- `metricName String`: The metrics name
|
||||
- `metricUnit Object`: The metrics GQL unit object
|
||||
- `nativeScope String`: The metrics native scope
|
||||
- `presetScopes [String]`: The preset scopes returned for this metric
|
||||
- `isShared Bool?`: If this job used shared resources; will adapt threshold indicators accordingly in downstream plots [Default: false]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
@component Job-View subcomponent; Wraps the statsTable in a TabPane and contains GQL query for scoped statsData
|
||||
@component Job-View subcomponent; Wraps the statsTable in a TabPane and contains GQL query for scoped statsData
|
||||
|
||||
Properties:
|
||||
- `job Object`: The job object
|
||||
- `clusters Object`: The clusters object
|
||||
- `tabActive bool`: Boolean if StatsTabe Tab is Active on Creation
|
||||
-->
|
||||
Properties:
|
||||
- `job Object`: The job object
|
||||
- `clusters Object`: The clusters object
|
||||
- `tabActive bool`: Boolean if StatsTabe Tab is Active on Creation
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,215 +1,215 @@
|
||||
<!--
|
||||
@component Job Footprint Bar component; Displays job footprint db data as bars relative to thresholds. Displays quality indicators and tooltips.
|
||||
@component Job Footprint Bar component; Displays job footprint db data as bars relative to thresholds. Displays quality indicators and tooltips.
|
||||
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
-->
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import {
|
||||
CardBody,
|
||||
Progress,
|
||||
Icon,
|
||||
Tooltip,
|
||||
Row,
|
||||
Col
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { findJobFootprintThresholds } from "../../generic/utils.js";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let {job} = $props();
|
||||
|
||||
/* Derived */
|
||||
// Prepare Job Footprint Data Based On Values Saved In Database
|
||||
const jobFootprintData = $derived(buildFootprint(job?.footprint));
|
||||
|
||||
/* Functions */
|
||||
function buildFootprint(input) {
|
||||
let result = input?.map((jf) => {
|
||||
const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name);
|
||||
if (fmc) {
|
||||
// Unit
|
||||
const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "")
|
||||
|
||||
// Threshold / -Differences
|
||||
const fmt = findJobFootprintThresholds(job, jf.stat, fmc);
|
||||
|
||||
// Define basic data -> Value: Use as Provided
|
||||
const fmBase = {
|
||||
name: jf.name,
|
||||
stat: jf.stat,
|
||||
value: jf.value,
|
||||
unit: unit,
|
||||
peak: fmt.peak,
|
||||
dir: fmc.lowerIsBetter
|
||||
};
|
||||
|
||||
if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "alert")) {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "danger",
|
||||
message: `Footprint value way ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
||||
impact: 3
|
||||
};
|
||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "caution")) {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "warning",
|
||||
message: `Footprint value ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
||||
impact: 2,
|
||||
};
|
||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "normal")) {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "success",
|
||||
message: "Footprint value within expected thresholds.",
|
||||
impact: 1,
|
||||
};
|
||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "info",
|
||||
message:
|
||||
"Footprint value above expected normal threshold: Check for artifacts recommended.",
|
||||
impact: 0,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "secondary",
|
||||
message:
|
||||
"Footprint value above expected peak threshold: Check for artifacts!",
|
||||
impact: -1,
|
||||
};
|
||||
}
|
||||
} else { // No matching metric config: display as single value
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import {
|
||||
CardBody,
|
||||
Progress,
|
||||
Icon,
|
||||
Tooltip,
|
||||
Row,
|
||||
Col
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { findJobFootprintThresholds } from "../../generic/utils.js";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
job
|
||||
} = $props();
|
||||
|
||||
/* Derived */
|
||||
// Prepare Job Footprint Data Based On Values Saved In Database
|
||||
const jobFootprintData = $derived(buildFootprint(job?.footprint));
|
||||
|
||||
/* Functions */
|
||||
function buildFootprint(input) {
|
||||
let result = input?.map((jf) => {
|
||||
const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name);
|
||||
if (fmc) {
|
||||
// Unit
|
||||
const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "")
|
||||
|
||||
// Threshold / -Differences
|
||||
const fmt = findJobFootprintThresholds(job, jf.stat, fmc);
|
||||
|
||||
// Define basic data -> Value: Use as Provided
|
||||
const fmBase = {
|
||||
name: jf.name,
|
||||
stat: jf.stat,
|
||||
value: jf.value,
|
||||
unit: unit,
|
||||
peak: fmt.peak,
|
||||
dir: fmc.lowerIsBetter
|
||||
};
|
||||
|
||||
if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "alert")) {
|
||||
return {
|
||||
name: jf.name,
|
||||
stat: jf.stat,
|
||||
value: jf.value,
|
||||
...fmBase,
|
||||
color: "danger",
|
||||
message: `Footprint value way ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
||||
impact: 3
|
||||
};
|
||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "caution")) {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "warning",
|
||||
message: `Footprint value ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`,
|
||||
impact: 2,
|
||||
};
|
||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "normal")) {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "success",
|
||||
message: "Footprint value within expected thresholds.",
|
||||
impact: 1,
|
||||
};
|
||||
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "info",
|
||||
message:
|
||||
`No config for metric ${jf.name} found.`,
|
||||
impact: 4,
|
||||
"Footprint value above expected normal threshold: Check for artifacts recommended.",
|
||||
impact: 0,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...fmBase,
|
||||
color: "secondary",
|
||||
message:
|
||||
"Footprint value above expected peak threshold: Check for artifacts!",
|
||||
impact: -1,
|
||||
};
|
||||
}
|
||||
}).sort(function (a, b) { // Sort by impact value primarily, within impact sort name alphabetically
|
||||
return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
|
||||
});;
|
||||
} else { // No matching metric config: display as single value
|
||||
return {
|
||||
name: jf.name,
|
||||
stat: jf.stat,
|
||||
value: jf.value,
|
||||
message:
|
||||
`No config for metric ${jf.name} found.`,
|
||||
impact: 4,
|
||||
};
|
||||
}
|
||||
}).sort(function (a, b) { // Sort by impact value primarily, within impact sort name alphabetically
|
||||
return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
|
||||
});;
|
||||
|
||||
return result
|
||||
};
|
||||
|
||||
function evalFootprint(value, thresholds, lowerIsBetter, level) {
|
||||
// Handle Metrics in which less value is better
|
||||
switch (level) {
|
||||
case "peak":
|
||||
if (lowerIsBetter)
|
||||
return false; // metric over peak -> return false to trigger impact -1
|
||||
else return value <= thresholds.peak && value > thresholds.normal;
|
||||
case "alert":
|
||||
if (lowerIsBetter)
|
||||
return value <= thresholds.peak && value >= thresholds.alert;
|
||||
else return value <= thresholds.alert && value >= 0;
|
||||
case "caution":
|
||||
if (lowerIsBetter)
|
||||
return value < thresholds.alert && value >= thresholds.caution;
|
||||
else return value <= thresholds.caution && value > thresholds.alert;
|
||||
case "normal":
|
||||
if (lowerIsBetter)
|
||||
return value < thresholds.caution && value >= 0;
|
||||
else return value <= thresholds.normal && value > thresholds.caution;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function evalFootprint(value, thresholds, lowerIsBetter, level) {
|
||||
// Handle Metrics in which less value is better
|
||||
switch (level) {
|
||||
case "peak":
|
||||
if (lowerIsBetter)
|
||||
return false; // metric over peak -> return false to trigger impact -1
|
||||
else return value <= thresholds.peak && value > thresholds.normal;
|
||||
case "alert":
|
||||
if (lowerIsBetter)
|
||||
return value <= thresholds.peak && value >= thresholds.alert;
|
||||
else return value <= thresholds.alert && value >= 0;
|
||||
case "caution":
|
||||
if (lowerIsBetter)
|
||||
return value < thresholds.alert && value >= thresholds.caution;
|
||||
else return value <= thresholds.caution && value > thresholds.alert;
|
||||
case "normal":
|
||||
if (lowerIsBetter)
|
||||
return value < thresholds.caution && value >= 0;
|
||||
else return value <= thresholds.normal && value > thresholds.caution;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardBody>
|
||||
{#if jobFootprintData.length === 0}
|
||||
<div class="text-center">No footprint data for job available.</div>
|
||||
{:else}
|
||||
{#each jobFootprintData as fpd, index}
|
||||
{#if fpd.impact !== 4}
|
||||
<div class="mb-1 d-flex justify-content-between">
|
||||
<div> <b>{fpd.name} ({fpd.stat})</b></div>
|
||||
<div
|
||||
class="cursor-help d-inline-flex"
|
||||
id={`footprint-${job.jobId}-${index}`}
|
||||
>
|
||||
<div class="mx-1">
|
||||
{#if fpd.impact === 3}
|
||||
<Icon name="exclamation-triangle-fill" class="text-danger" />
|
||||
{:else if fpd.impact === 2}
|
||||
<Icon name="exclamation-triangle" class="text-warning" />
|
||||
{:else if fpd.impact === 0}
|
||||
<Icon name="info-circle" class="text-info" />
|
||||
{:else if fpd.impact === -1}
|
||||
<Icon name="info-circle-fill" class="text-danger" />
|
||||
{/if}
|
||||
{#if fpd.impact === 3}
|
||||
<Icon name="emoji-frown" class="text-danger" />
|
||||
{:else if fpd.impact === 2}
|
||||
<Icon name="emoji-neutral" class="text-warning" />
|
||||
{:else if fpd.impact === 1}
|
||||
<Icon name="emoji-smile" class="text-success" />
|
||||
{:else if fpd.impact === 0}
|
||||
<Icon name="emoji-smile" class="text-info" />
|
||||
{:else if fpd.impact === -1}
|
||||
<Icon name="emoji-dizzy" class="text-danger" />
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{fpd.value} / {fpd.peak}
|
||||
{fpd.unit}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
target={`footprint-${job.jobId}-${index}`}
|
||||
placement="right"
|
||||
>{fpd.message}</Tooltip
|
||||
>
|
||||
</div>
|
||||
<Row cols={12} class={(jobFootprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}>
|
||||
{#if fpd.dir}
|
||||
<Col xs="1">
|
||||
<Icon name="caret-left-fill" />
|
||||
</Col>
|
||||
{/if}
|
||||
<Col xs="11" class="align-content-center">
|
||||
<Progress value={fpd.value} max={fpd.peak} color={fpd.color} />
|
||||
</Col>
|
||||
{#if !fpd.dir}
|
||||
<Col xs="1">
|
||||
<Icon name="caret-right-fill" />
|
||||
</Col>
|
||||
{/if}
|
||||
</Row>
|
||||
{:else}
|
||||
<div class="mb-1 d-flex justify-content-between">
|
||||
<div>
|
||||
<b>{fpd.name} ({fpd.stat})</b>
|
||||
</div>
|
||||
<div
|
||||
class="cursor-help d-inline-flex"
|
||||
id={`footprint-${job.jobId}-${index}`}
|
||||
>
|
||||
<div class="mx-1">
|
||||
<Icon name="info-circle"/>
|
||||
</div>
|
||||
<div>
|
||||
{fpd.value}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
target={`footprint-${job.jobId}-${index}`}
|
||||
placement="right"
|
||||
>{fpd.message}</Tooltip
|
||||
>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#if jobFootprintData.length === 0}
|
||||
<div class="text-center">No footprint data for job available.</div>
|
||||
{:else}
|
||||
{#each jobFootprintData as fpd, index}
|
||||
{#if fpd.impact !== 4}
|
||||
<div class="mb-1 d-flex justify-content-between">
|
||||
<div> <b>{fpd.name} ({fpd.stat})</b></div>
|
||||
<div
|
||||
class="cursor-help d-inline-flex"
|
||||
id={`footprint-${job.jobId}-${index}`}
|
||||
>
|
||||
<div class="mx-1">
|
||||
{#if fpd.impact === 3}
|
||||
<Icon name="exclamation-triangle-fill" class="text-danger" />
|
||||
{:else if fpd.impact === 2}
|
||||
<Icon name="exclamation-triangle" class="text-warning" />
|
||||
{:else if fpd.impact === 0}
|
||||
<Icon name="info-circle" class="text-info" />
|
||||
{:else if fpd.impact === -1}
|
||||
<Icon name="info-circle-fill" class="text-danger" />
|
||||
{/if}
|
||||
{#if fpd.impact === 3}
|
||||
<Icon name="emoji-frown" class="text-danger" />
|
||||
{:else if fpd.impact === 2}
|
||||
<Icon name="emoji-neutral" class="text-warning" />
|
||||
{:else if fpd.impact === 1}
|
||||
<Icon name="emoji-smile" class="text-success" />
|
||||
{:else if fpd.impact === 0}
|
||||
<Icon name="emoji-smile" class="text-info" />
|
||||
{:else if fpd.impact === -1}
|
||||
<Icon name="emoji-dizzy" class="text-danger" />
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{fpd.value} / {fpd.peak}
|
||||
{fpd.unit}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
target={`footprint-${job.jobId}-${index}`}
|
||||
placement="right"
|
||||
>{fpd.message}</Tooltip>
|
||||
</div>
|
||||
<Row cols={12} class={(jobFootprintData.length == (index + 1)) ? 'mb-0' : 'mb-2'}>
|
||||
{#if fpd.dir}
|
||||
<Col xs="1">
|
||||
<Icon name="caret-left-fill" />
|
||||
</Col>
|
||||
{/if}
|
||||
<Col xs="11" class="align-content-center">
|
||||
<Progress value={fpd.value} max={fpd.peak} color={fpd.color} />
|
||||
</Col>
|
||||
{#if !fpd.dir}
|
||||
<Col xs="1">
|
||||
<Icon name="caret-right-fill" />
|
||||
</Col>
|
||||
{/if}
|
||||
</Row>
|
||||
{:else}
|
||||
<div class="mb-1 d-flex justify-content-between">
|
||||
<div>
|
||||
<b>{fpd.name} ({fpd.stat})</b>
|
||||
</div>
|
||||
<div
|
||||
class="cursor-help d-inline-flex"
|
||||
id={`footprint-${job.jobId}-${index}`}
|
||||
>
|
||||
<div class="mx-1">
|
||||
<Icon name="info-circle"/>
|
||||
</div>
|
||||
<div>
|
||||
{fpd.value}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
target={`footprint-${job.jobId}-${index}`}
|
||||
placement="right"
|
||||
>{fpd.message}</Tooltip>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</CardBody>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,64 +1,66 @@
|
||||
<!--
|
||||
@component Job Footprint Polar Plot component; Displays queried job metric statistics polar plot.
|
||||
@component Job Footprint Polar Plot component; Displays queried job metric statistics polar plot.
|
||||
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
-->
|
||||
Properties:
|
||||
- `job Object`: The GQL job object
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import {
|
||||
queryStore,
|
||||
gql,
|
||||
getContextClient
|
||||
} from "@urql/svelte";
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Spinner
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { findJobFootprintThresholds } from "../../generic/utils.js";
|
||||
import Polar from "../../generic/plots/Polar.svelte";
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import {
|
||||
queryStore,
|
||||
gql,
|
||||
getContextClient
|
||||
} from "@urql/svelte";
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Spinner
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
import { findJobFootprintThresholds } from "../../generic/utils.js";
|
||||
import Polar from "../../generic/plots/Polar.svelte";
|
||||
|
||||
/* Svelte 5 Props */
|
||||
let { job } = $props();
|
||||
|
||||
/* Const Init */
|
||||
// Metric Names Configured To Be Footprints For (sub)Cluster
|
||||
const clusterFootprintMetrics = getContext("clusters")
|
||||
/* Svelte 5 Props */
|
||||
let {
|
||||
job
|
||||
} = $props();
|
||||
|
||||
/* Const Init */
|
||||
// Metric Names Configured To Be Footprints For (sub)Cluster
|
||||
const clusterFootprintMetrics = getContext("clusters")
|
||||
.find((c) => c.name == job.cluster)?.subClusters
|
||||
.find((sc) => sc.name == job.subCluster)?.footprint || []
|
||||
|
||||
// Get Scaled Peak Threshold Based on Footprint Type ([min, max, avg]) and Job Exclusivity
|
||||
const polarMetrics = getContext("globalMetrics").reduce((pms, gm) => {
|
||||
// Get Scaled Peak Threshold Based on Footprint Type ([min, max, avg]) and Job Exclusivity
|
||||
const polarMetrics = getContext("globalMetrics").reduce((pms, gm) => {
|
||||
if (clusterFootprintMetrics.includes(gm.name)) {
|
||||
const fmt = findJobFootprintThresholds(job, gm.footprint, getContext("getMetricConfig")(job.cluster, job.subCluster, gm.name));
|
||||
pms.push({ name: gm.name, peak: fmt ? fmt.peak : null });
|
||||
const fmt = findJobFootprintThresholds(job, gm.footprint, getContext("getMetricConfig")(job.cluster, job.subCluster, gm.name));
|
||||
pms.push({ name: gm.name, peak: fmt ? fmt.peak : null });
|
||||
}
|
||||
return pms;
|
||||
}, [])
|
||||
}, [])
|
||||
|
||||
// Pull All Series For Footprint Metrics Statistics Only On Node Scope
|
||||
const client = getContextClient();
|
||||
const polarQuery = gql`
|
||||
query ($dbid: ID!, $selectedMetrics: [String!]!) {
|
||||
jobStats(id: $dbid, metrics: $selectedMetrics) {
|
||||
name
|
||||
data {
|
||||
min
|
||||
avg
|
||||
max
|
||||
}
|
||||
// Pull All Series For Footprint Metrics Statistics Only On Node Scope
|
||||
const client = getContextClient();
|
||||
const polarQuery = gql`
|
||||
query ($dbid: ID!, $selectedMetrics: [String!]!) {
|
||||
jobStats(id: $dbid, metrics: $selectedMetrics) {
|
||||
name
|
||||
data {
|
||||
min
|
||||
avg
|
||||
max
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/* Derived */
|
||||
const polarData = $derived(queryStore({
|
||||
client: client,
|
||||
query: polarQuery,
|
||||
variables:{ dbid: job.id, selectedMetrics: clusterFootprintMetrics },
|
||||
}));
|
||||
/* Derived */
|
||||
const polarData = $derived(queryStore({
|
||||
client: client,
|
||||
query: polarQuery,
|
||||
variables:{ dbid: job.id, selectedMetrics: clusterFootprintMetrics },
|
||||
}));
|
||||
</script>
|
||||
|
||||
<CardBody>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--:
|
||||
@component Job-View subcomponent; display table of metric data statistics with selectable scopes
|
||||
@component Job-View subcomponent; display table of metric data statistics with selectable scopes
|
||||
|
||||
Properties:
|
||||
- `hosts [String]`: The list of hostnames of this job
|
||||
- `jobStats Object`: The data object
|
||||
- `selectedMetrics [String]`: The selected metrics
|
||||
-->
|
||||
Properties:
|
||||
- `hosts [String]`: The list of hostnames of this job
|
||||
- `jobStats Object`: The data object
|
||||
- `selectedMetrics [String]`: The selected metrics
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
@@ -123,7 +123,6 @@
|
||||
return s.dir != "up" ? s1[stat] - s2[stat] : s2[stat] - s1[stat];
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<Table class="mb-0">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!--
|
||||
@component Job-View subcomponent; Single Statistics entry component for statstable
|
||||
@component Job-View subcomponent; Single Statistics entry component for statstable
|
||||
|
||||
Properties:
|
||||
- `data [Object]`: The jobs statsdata for host-metric-scope
|
||||
- `scope String`: The selected scope
|
||||
-->
|
||||
Properties:
|
||||
- `data [Object]`: The jobs statsdata for host-metric-scope
|
||||
- `scope String`: The selected scope
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Icon } from "@sveltestrap/sveltestrap";
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<!--
|
||||
@component Cluster Per Node List component; renders current state of SELECTABLE metrics for ALL nodes
|
||||
@component Cluster Per Node List component; renders current state of SELECTABLE metrics for ALL nodes
|
||||
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `subCluster String`: The nodes' subCluster
|
||||
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
||||
- `selectedMetrics [String]`: The array of selected metrics
|
||||
- `systemUnits Object`: The object of metric units
|
||||
-->
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `subCluster String`: The nodes' subCluster [Default: ""]
|
||||
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
||||
- `selectedMetrics [String]`: The array of selected metrics [Default []]
|
||||
- `selectedResolution Number?`: The selected data resolution [Default: 0]
|
||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
||||
- `presetSystemUnits Object`: The object of metric units [Default: null]
|
||||
- `from Date?`: The selected "from" date [Default: null]
|
||||
- `to Date?`: The selected "to" date [Default: null]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { queryStore, gql, getContextClient, mutationStore } from "@urql/svelte";
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<!--
|
||||
@component Cluster Per Node Overview component; renders current state of ONE metric for ALL nodes
|
||||
@component Cluster Per Node Overview component; renders current state of ONE metric for ALL nodes
|
||||
|
||||
Properties:
|
||||
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
||||
- `cluster String`: The cluster to show status information for
|
||||
- `selectedMetric String?`: The selectedMetric input [Default: ""]
|
||||
-->
|
||||
Properties:
|
||||
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
|
||||
- `cluster String`: The cluster to show status information for
|
||||
- `selectedMetric String?`: The selectedMetric input [Default: ""]
|
||||
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
|
||||
- `from Date?`: The selected "from" date [Default: null]
|
||||
- `to Date?`: The selected "to" date [Default: null]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<!--
|
||||
@component Displays node info, serves links to single node page and lists
|
||||
@component Displays node info, serves links to single node page and lists
|
||||
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `subCluster String`: The nodes' subCluster
|
||||
- `cluster String`: The nodes' hostname
|
||||
-->
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `subCluster String`: The nodes' subCluster
|
||||
- `hostname String`: The nodes' hostname
|
||||
- `dataHealth [Bool]`: Array of Booleans depicting state of returned data per metric
|
||||
- `nodeJobsData [Object]`: Data returned by GQL for jobs runninig on this node [Default: null]
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
@component Data row for a single node displaying metric plots
|
||||
@component Data row for a single node displaying metric plots
|
||||
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `nodeData Object`: The node data object including metric data
|
||||
- `selectedMetrics [String]`: The array of selected metrics
|
||||
-->
|
||||
Properties:
|
||||
- `cluster String`: The nodes' cluster
|
||||
- `nodeData Object`: The node data object including metric data
|
||||
- `selectedMetrics [String]`: The array of selected metrics
|
||||
-->
|
||||
|
||||
<script>
|
||||
import {
|
||||
|
||||
Reference in New Issue
Block a user