mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-06-08 08:33:49 +02:00
Migrate user list and analysis view
This commit is contained in:
parent
0b529a5c3c
commit
703556d893
@ -37,14 +37,12 @@
|
|||||||
import ScatterPlot from "./generic/plots/Scatter.svelte";
|
import ScatterPlot from "./generic/plots/Scatter.svelte";
|
||||||
import RooflineHeatmap from "./generic/plots/RooflineHeatmap.svelte";
|
import RooflineHeatmap from "./generic/plots/RooflineHeatmap.svelte";
|
||||||
|
|
||||||
const { query: initq } = init();
|
/* Svelte 5 Props */
|
||||||
|
let { filterPresets } = $props();
|
||||||
export let filterPresets;
|
|
||||||
|
|
||||||
// By default, look at the jobs of the last 6 hours:
|
// By default, look at the jobs of the last 6 hours:
|
||||||
if (filterPresets?.startTime == null) {
|
if (filterPresets?.startTime == null) {
|
||||||
if (filterPresets == null) filterPresets = {};
|
if (filterPresets == null) filterPresets = {};
|
||||||
|
|
||||||
let now = new Date(Date.now());
|
let now = new Date(Date.now());
|
||||||
let hourAgo = new Date(now);
|
let hourAgo = new Date(now);
|
||||||
hourAgo.setHours(hourAgo.getHours() - 6);
|
hourAgo.setHours(hourAgo.getHours() - 6);
|
||||||
@ -54,27 +52,12 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let cluster;
|
/* Const Init */
|
||||||
let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
const { query: initq } = init();
|
||||||
let jobFilters = [];
|
const client = getContextClient();
|
||||||
let rooflineMaxY;
|
|
||||||
let colWidth1, colWidth2;
|
|
||||||
let numBins = 50;
|
|
||||||
let maxY = -1;
|
|
||||||
|
|
||||||
const initialized = getContext("initialized");
|
const initialized = getContext("initialized");
|
||||||
const globalMetrics = getContext("globalMetrics");
|
const globalMetrics = getContext("globalMetrics");
|
||||||
const ccconfig = getContext("cc-config");
|
const ccconfig = getContext("cc-config");
|
||||||
|
|
||||||
let metricsInHistograms = ccconfig.analysis_view_histogramMetrics,
|
|
||||||
metricsInScatterplots = ccconfig.analysis_view_scatterPlotMetrics;
|
|
||||||
|
|
||||||
$: metrics = [
|
|
||||||
...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]),
|
|
||||||
];
|
|
||||||
|
|
||||||
$: clusterName = cluster?.name ? cluster.name : cluster;
|
|
||||||
|
|
||||||
const sortOptions = [
|
const sortOptions = [
|
||||||
{ key: "totalWalltime", label: "Walltime" },
|
{ key: "totalWalltime", label: "Walltime" },
|
||||||
{ key: "totalNodeHours", label: "Node Hours" },
|
{ key: "totalNodeHours", label: "Node Hours" },
|
||||||
@ -86,7 +69,22 @@
|
|||||||
{ key: "project", label: "Project ID" },
|
{ key: "project", label: "Project ID" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let sortSelection =
|
/* Var Init */
|
||||||
|
let availableMetrics = [];
|
||||||
|
let metricUnits = {};
|
||||||
|
let metricScopes = {};
|
||||||
|
let rooflineMaxY;
|
||||||
|
let cluster;
|
||||||
|
let colWidth1, colWidth2;
|
||||||
|
let numBins = 50;
|
||||||
|
let maxY = -1;
|
||||||
|
|
||||||
|
/* State Init */
|
||||||
|
let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
||||||
|
let jobFilters = $state([]);
|
||||||
|
let metricsInHistograms = $state(ccconfig.analysis_view_histogramMetrics)
|
||||||
|
let metricsInScatterplots = $state(ccconfig.analysis_view_scatterPlotMetrics)
|
||||||
|
let sortSelection = $state(
|
||||||
sortOptions.find(
|
sortOptions.find(
|
||||||
(option) =>
|
(option) =>
|
||||||
option.key ==
|
option.key ==
|
||||||
@ -94,8 +92,9 @@
|
|||||||
) ||
|
) ||
|
||||||
sortOptions.find(
|
sortOptions.find(
|
||||||
(option) => option.key == ccconfig.analysis_view_selectedTopCategory,
|
(option) => option.key == ccconfig.analysis_view_selectedTopCategory,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
let groupSelection =
|
let groupSelection = $state(
|
||||||
groupOptions.find(
|
groupOptions.find(
|
||||||
(option) =>
|
(option) =>
|
||||||
option.key ==
|
option.key ==
|
||||||
@ -103,8 +102,10 @@
|
|||||||
) ||
|
) ||
|
||||||
groupOptions.find(
|
groupOptions.find(
|
||||||
(option) => option.key == ccconfig.analysis_view_selectedTopEntity,
|
(option) => option.key == ccconfig.analysis_view_selectedTopEntity,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Init Function */
|
||||||
getContext("on-init")(({ data }) => {
|
getContext("on-init")(({ data }) => {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
cluster = data.clusters.find((c) => c.name == filterPresets.cluster);
|
cluster = data.clusters.find((c) => c.name == filterPresets.cluster);
|
||||||
@ -121,9 +122,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const client = getContextClient();
|
/* Derived Vars */
|
||||||
|
let clusterName = $derived(cluster?.name ? cluster.name : cluster);
|
||||||
|
let metrics = $derived(
|
||||||
|
[...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()])]
|
||||||
|
);
|
||||||
|
|
||||||
$: statsQuery = queryStore({
|
let statsQuery = $derived(
|
||||||
|
queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query ($jobFilters: [JobFilter!]!) {
|
query ($jobFilters: [JobFilter!]!) {
|
||||||
@ -146,9 +152,11 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: { jobFilters },
|
variables: { jobFilters },
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
$: topQuery = queryStore({
|
let topQuery = $derived(
|
||||||
|
queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query (
|
query (
|
||||||
@ -178,10 +186,12 @@
|
|||||||
sortBy: sortSelection.key.toUpperCase(),
|
sortBy: sortSelection.key.toUpperCase(),
|
||||||
groupBy: groupSelection.key.toUpperCase(),
|
groupBy: groupSelection.key.toUpperCase(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Note: Different footprints than those saved in DB per Job -> Caused by Legacy Naming
|
// Note: Different footprints than those saved in DB per Job -> Caused by Legacy Naming
|
||||||
$: footprintsQuery = queryStore({
|
let footprintsQuery = $derived(
|
||||||
|
queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) {
|
query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) {
|
||||||
@ -199,9 +209,11 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: { jobFilters, metrics },
|
variables: { jobFilters, metrics },
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
$: rooflineQuery = queryStore({
|
let rooflineQuery = $derived(
|
||||||
|
queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query (
|
query (
|
||||||
@ -233,8 +245,21 @@
|
|||||||
maxX: 1000,
|
maxX: 1000,
|
||||||
maxY,
|
maxY,
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Reactive Effects */
|
||||||
|
$effect(() => {
|
||||||
|
loadMetrics($initialized)
|
||||||
|
});
|
||||||
|
$effect(() => {
|
||||||
|
updateEntityConfiguration(groupSelection.key);
|
||||||
|
});
|
||||||
|
$effect(() => {
|
||||||
|
updateCategoryConfiguration(sortSelection.key);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
const updateConfigurationMutation = ({ name, value }) => {
|
const updateConfigurationMutation = ({ name, value }) => {
|
||||||
return mutationStore({
|
return mutationStore({
|
||||||
client: client,
|
client: client,
|
||||||
@ -287,9 +312,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableMetrics = [];
|
|
||||||
let metricUnits = {};
|
|
||||||
let metricScopes = {};
|
|
||||||
function loadMetrics(isInitialized) {
|
function loadMetrics(isInitialized) {
|
||||||
if (!isInitialized) return
|
if (!isInitialized) return
|
||||||
availableMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster.name))]
|
availableMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster.name))]
|
||||||
@ -299,10 +321,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: loadMetrics($initialized)
|
/* On Mount */
|
||||||
$: updateEntityConfiguration(groupSelection.key);
|
|
||||||
$: updateCategoryConfiguration(sortSelection.key);
|
|
||||||
|
|
||||||
onMount(() => filterComponent.updateFilters());
|
onMount(() => filterComponent.updateFilters());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -329,7 +348,7 @@
|
|||||||
{filterPresets}
|
{filterPresets}
|
||||||
disableClusterSelection={true}
|
disableClusterSelection={true}
|
||||||
startTimeQuickSelect={true}
|
startTimeQuickSelect={true}
|
||||||
on:update-filters={({ detail }) => {
|
applyFilters={(detail) => {
|
||||||
jobFilters = detail.filters;
|
jobFilters = detail.filters;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -31,10 +31,8 @@
|
|||||||
} from "./generic/utils.js";
|
} from "./generic/utils.js";
|
||||||
import Filters from "./generic/Filters.svelte";
|
import Filters from "./generic/Filters.svelte";
|
||||||
|
|
||||||
const {} = init();
|
/* Svelte 5 Props */
|
||||||
|
let { type, filterPresets } = $props();
|
||||||
export let type;
|
|
||||||
export let filterPresets;
|
|
||||||
|
|
||||||
// By default, look at the jobs of the last 30 days:
|
// By default, look at the jobs of the last 30 days:
|
||||||
if (filterPresets?.startTime == null) {
|
if (filterPresets?.startTime == null) {
|
||||||
@ -51,13 +49,19 @@
|
|||||||
"Invalid list type provided!",
|
"Invalid list type provided!",
|
||||||
);
|
);
|
||||||
|
|
||||||
let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
/* Const Init */
|
||||||
let jobFilters = [];
|
const {} = init();
|
||||||
let nameFilter = "";
|
|
||||||
let sorting = { field: "totalJobs", direction: "down" };
|
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
$: stats = queryStore({
|
|
||||||
|
/* State Init*/
|
||||||
|
let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
||||||
|
let jobFilters = $state([]);
|
||||||
|
let nameFilter = $state("");
|
||||||
|
let sorting = $state({ field: "totalJobs", direction: "down" });
|
||||||
|
|
||||||
|
/* Derived Vars */
|
||||||
|
let stats = $derived(
|
||||||
|
queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query($jobFilters: [JobFilter!]!) {
|
query($jobFilters: [JobFilter!]!) {
|
||||||
@ -71,15 +75,12 @@
|
|||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
variables: { jobFilters },
|
variables: { jobFilters },
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
function changeSorting(event, field) {
|
/* Functions */
|
||||||
let target = event.target;
|
function changeSorting(field) {
|
||||||
while (target.tagName != "BUTTON") target = target.parentElement;
|
sorting = { field, direction: sorting?.direction == "down" ? "up" : "down" };
|
||||||
|
|
||||||
let direction = target.children[0].className.includes("up") ? "down" : "up";
|
|
||||||
target.children[0].className = `bi-sort-numeric-${direction}`;
|
|
||||||
sorting = { field, direction };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sort(stats, sorting, nameFilter) {
|
function sort(stats, sorting, nameFilter) {
|
||||||
@ -87,10 +88,10 @@
|
|||||||
? (a, b) => b.id.localeCompare(a.id)
|
? (a, b) => b.id.localeCompare(a.id)
|
||||||
: (a, b) => a.id.localeCompare(b.id)
|
: (a, b) => a.id.localeCompare(b.id)
|
||||||
|
|
||||||
// "-50": Forces empty strings to the end of the list
|
// Force empty or undefined strings to the end of the list
|
||||||
const nameCmp = sorting.direction == "up"
|
const nameCmp = sorting.direction == "up"
|
||||||
? (a, b) => (a.name == '') ? -50 : b.name.localeCompare(a.name)
|
? (a, b) => !a?.name ? 1 : (!b?.name ? -1 : (b.name.localeCompare(a.name)))
|
||||||
: (a, b) => (b.name == '') ? -50 : a.name.localeCompare(b.name)
|
: (a, b) => !a?.name ? 1 : (!b?.name ? -1 : (a.name.localeCompare(b.name)))
|
||||||
|
|
||||||
const intCmp = sorting.direction == "up"
|
const intCmp = sorting.direction == "up"
|
||||||
? (a, b) => a[sorting.field] - b[sorting.field]
|
? (a, b) => a[sorting.field] - b[sorting.field]
|
||||||
@ -105,6 +106,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* On Mount */
|
||||||
onMount(() => filterComponent.updateFilters());
|
onMount(() => filterComponent.updateFilters());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -129,7 +131,7 @@
|
|||||||
{filterPresets}
|
{filterPresets}
|
||||||
startTimeQuickSelect={true}
|
startTimeQuickSelect={true}
|
||||||
menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up"
|
menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up"
|
||||||
on:update-filters={({ detail }) => {
|
applyFilters={(detail) => {
|
||||||
jobFilters = detail.filters;
|
jobFilters = detail.filters;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -147,9 +149,14 @@
|
|||||||
<Button
|
<Button
|
||||||
color={sorting.field == "id" ? "primary" : "light"}
|
color={sorting.field == "id" ? "primary" : "light"}
|
||||||
size="sm"
|
size="sm"
|
||||||
on:click={(e) => changeSorting(e, "id")}
|
onclick={() => changeSorting("id")}
|
||||||
>
|
>
|
||||||
<Icon name="sort-numeric-down" />
|
{#if sorting?.field == "id"}
|
||||||
|
<!-- Note on Icon-Name: Arrow-indicator always down, only alpha-indicator switches -->
|
||||||
|
<Icon name={`sort-alpha-${sorting?.direction == 'down' ? 'down' : 'down-alt'}`} />
|
||||||
|
{:else}
|
||||||
|
<Icon name="three-dots-vertical" />
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</th>
|
</th>
|
||||||
{#if type == "USER"}
|
{#if type == "USER"}
|
||||||
@ -158,9 +165,13 @@
|
|||||||
<Button
|
<Button
|
||||||
color={sorting.field == "name" ? "primary" : "light"}
|
color={sorting.field == "name" ? "primary" : "light"}
|
||||||
size="sm"
|
size="sm"
|
||||||
on:click={(e) => changeSorting(e, "name")}
|
onclick={() => changeSorting("name")}
|
||||||
>
|
>
|
||||||
<Icon name="sort-numeric-down" />
|
{#if sorting?.field == "name"}
|
||||||
|
<Icon name={`sort-alpha-${sorting?.direction == 'down' ? 'down' : 'down-alt'}`} />
|
||||||
|
{:else}
|
||||||
|
<Icon name="three-dots-vertical" />
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</th>
|
</th>
|
||||||
{/if}
|
{/if}
|
||||||
@ -169,9 +180,14 @@
|
|||||||
<Button
|
<Button
|
||||||
color={sorting.field == "totalJobs" ? "primary" : "light"}
|
color={sorting.field == "totalJobs" ? "primary" : "light"}
|
||||||
size="sm"
|
size="sm"
|
||||||
on:click={(e) => changeSorting(e, "totalJobs")}
|
onclick={() => changeSorting("totalJobs")}
|
||||||
>
|
>
|
||||||
<Icon name="sort-numeric-down" />
|
{#if sorting?.field == "totalJobs"}
|
||||||
|
<!-- Note on Icon-Name: Arrow-indicator always down, only numeric-indicator switches -->
|
||||||
|
<Icon name={`sort-numeric-${sorting?.direction == 'down' ? 'down-alt' : 'down'}`} />
|
||||||
|
{:else}
|
||||||
|
<Icon name="three-dots-vertical" />
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
@ -179,9 +195,13 @@
|
|||||||
<Button
|
<Button
|
||||||
color={sorting.field == "totalWalltime" ? "primary" : "light"}
|
color={sorting.field == "totalWalltime" ? "primary" : "light"}
|
||||||
size="sm"
|
size="sm"
|
||||||
on:click={(e) => changeSorting(e, "totalWalltime")}
|
onclick={() => changeSorting("totalWalltime")}
|
||||||
>
|
>
|
||||||
<Icon name="sort-numeric-down" />
|
{#if sorting?.field == "totalWalltime"}
|
||||||
|
<Icon name={`sort-numeric-${sorting?.direction == 'down' ? 'down-alt' : 'down'}`} />
|
||||||
|
{:else}
|
||||||
|
<Icon name="three-dots-vertical" />
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
@ -189,9 +209,13 @@
|
|||||||
<Button
|
<Button
|
||||||
color={sorting.field == "totalCoreHours" ? "primary" : "light"}
|
color={sorting.field == "totalCoreHours" ? "primary" : "light"}
|
||||||
size="sm"
|
size="sm"
|
||||||
on:click={(e) => changeSorting(e, "totalCoreHours")}
|
onclick={() => changeSorting("totalCoreHours")}
|
||||||
>
|
>
|
||||||
<Icon name="sort-numeric-down" />
|
{#if sorting?.field == "totalCoreHours"}
|
||||||
|
<Icon name={`sort-numeric-${sorting?.direction == 'down' ? 'down-alt' : 'down'}`} />
|
||||||
|
{:else}
|
||||||
|
<Icon name="three-dots-vertical" />
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
@ -199,9 +223,13 @@
|
|||||||
<Button
|
<Button
|
||||||
color={sorting.field == "totalAccHours" ? "primary" : "light"}
|
color={sorting.field == "totalAccHours" ? "primary" : "light"}
|
||||||
size="sm"
|
size="sm"
|
||||||
on:click={(e) => changeSorting(e, "totalAccHours")}
|
onclick={() => changeSorting("totalAccHours")}
|
||||||
>
|
>
|
||||||
<Icon name="sort-numeric-down" />
|
{#if sorting?.field == "totalAccHours"}
|
||||||
|
<Icon name={`sort-numeric-${sorting?.direction == 'down' ? 'down-alt' : 'down'}`} />
|
||||||
|
{:else}
|
||||||
|
<Icon name="three-dots-vertical" />
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -42,38 +42,55 @@
|
|||||||
import TextFilter from "./generic/helper/TextFilter.svelte"
|
import TextFilter from "./generic/helper/TextFilter.svelte"
|
||||||
import Refresher from "./generic/helper/Refresher.svelte";
|
import Refresher from "./generic/helper/Refresher.svelte";
|
||||||
|
|
||||||
|
/* Svelte 5 Props */
|
||||||
|
let { user, filterPresets } = $props();
|
||||||
|
|
||||||
|
/* Const Init */
|
||||||
const { query: initq } = init();
|
const { query: initq } = init();
|
||||||
|
|
||||||
const ccconfig = getContext("cc-config");
|
const ccconfig = getContext("cc-config");
|
||||||
|
|
||||||
export let user;
|
|
||||||
export let filterPresets;
|
|
||||||
|
|
||||||
let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
|
||||||
let jobList;
|
|
||||||
let jobFilters = [];
|
|
||||||
let matchedListJobs = 0;
|
|
||||||
let sorting = { field: "startTime", type: "col", order: "DESC" },
|
|
||||||
isSortingOpen = false;
|
|
||||||
let metrics = ccconfig.plot_list_selectedMetrics,
|
|
||||||
isMetricsSelectionOpen = false;
|
|
||||||
let isHistogramSelectionOpen = false;
|
|
||||||
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null;
|
|
||||||
let showFootprint = filterPresets.cluster
|
|
||||||
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
|
|
||||||
: !!ccconfig.plot_list_showFootprint;
|
|
||||||
|
|
||||||
let numDurationBins = "1h";
|
|
||||||
let numMetricBins = 10;
|
|
||||||
let durationBinOptions = ["1m","10m","1h","6h","12h"];
|
|
||||||
let metricBinOptions = [10, 20, 50, 100];
|
|
||||||
|
|
||||||
$: selectedHistograms = selectedCluster
|
|
||||||
? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
|
||||||
: ccconfig['user_view_histogramMetrics'] || [];
|
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
$: stats = queryStore({
|
const durationBinOptions = ["1m","10m","1h","6h","12h"];
|
||||||
|
const metricBinOptions = [10, 20, 50, 100];
|
||||||
|
|
||||||
|
/* State Init */
|
||||||
|
// List & Control Vars
|
||||||
|
let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the
|
||||||
|
let jobFilters = $state([]);
|
||||||
|
let jobList = $state(null);
|
||||||
|
let matchedListJobs = $state(0);
|
||||||
|
let isSortingOpen = $state(false);
|
||||||
|
let isMetricsSelectionOpen = $state(false);
|
||||||
|
let sorting = $state({ field: "startTime", type: "col", order: "DESC" });
|
||||||
|
let selectedCluster = $state(filterPresets?.cluster ? filterPresets.cluster : null);
|
||||||
|
let metrics = $state(filterPresets.cluster
|
||||||
|
? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] ||
|
||||||
|
ccconfig.plot_list_selectedMetrics
|
||||||
|
: ccconfig.plot_list_selectedMetrics
|
||||||
|
);
|
||||||
|
let showFootprint = $state(filterPresets.cluster
|
||||||
|
? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`]
|
||||||
|
: !!ccconfig.plot_list_showFootprint
|
||||||
|
);
|
||||||
|
|
||||||
|
// Histogram Vars
|
||||||
|
let isHistogramSelectionOpen = $state(false);
|
||||||
|
let numDurationBins = $state("1h");
|
||||||
|
let numMetricBins = $state(10);
|
||||||
|
|
||||||
|
// Compare Vars (TODO)
|
||||||
|
// let jobCompare = $state(null);
|
||||||
|
// let showCompare = $state(false);
|
||||||
|
// let selectedJobs = $state([]);
|
||||||
|
// let filterBuffer = $state([]);
|
||||||
|
// let matchedCompareJobs = $state(0);
|
||||||
|
|
||||||
|
/* Derived Vars */
|
||||||
|
let selectedHistograms = $derived(selectedCluster
|
||||||
|
? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] )
|
||||||
|
: ccconfig['user_view_histogramMetrics'] || []);
|
||||||
|
|
||||||
|
let stats = $derived(
|
||||||
|
queryStore({
|
||||||
client: client,
|
client: client,
|
||||||
query: gql`
|
query: gql`
|
||||||
query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) {
|
query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) {
|
||||||
@ -105,8 +122,10 @@
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins },
|
variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins },
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/* On Mount */
|
||||||
onMount(() => filterComponent.updateFilters());
|
onMount(() => filterComponent.updateFilters());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -129,13 +148,13 @@
|
|||||||
<Row cols={{ xs: 1, md: 2, lg: 6}} class="mb-3">
|
<Row cols={{ xs: 1, md: 2, lg: 6}} class="mb-3">
|
||||||
<Col class="mb-2 mb-lg-0">
|
<Col class="mb-2 mb-lg-0">
|
||||||
<ButtonGroup class="w-100">
|
<ButtonGroup class="w-100">
|
||||||
<Button outline color="primary" on:click={() => (isSortingOpen = true)}>
|
<Button outline color="primary" onclick={() => (isSortingOpen = true)}>
|
||||||
<Icon name="sort-up" /> Sorting
|
<Icon name="sort-up" /> Sorting
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
outline
|
outline
|
||||||
color="primary"
|
color="primary"
|
||||||
on:click={() => (isMetricsSelectionOpen = true)}
|
onclick={() => (isMetricsSelectionOpen = true)}
|
||||||
>
|
>
|
||||||
<Icon name="graph-up" /> Metrics
|
<Icon name="graph-up" /> Metrics
|
||||||
</Button>
|
</Button>
|
||||||
@ -143,11 +162,11 @@
|
|||||||
</Col>
|
</Col>
|
||||||
<Col lg="4" class="mb-1 mb-lg-0">
|
<Col lg="4" class="mb-1 mb-lg-0">
|
||||||
<Filters
|
<Filters
|
||||||
|
bind:this={filterComponent}
|
||||||
{filterPresets}
|
{filterPresets}
|
||||||
matchedJobs={matchedListJobs}
|
matchedJobs={matchedListJobs}
|
||||||
startTimeQuickSelect={true}
|
startTimeQuickSelect={true}
|
||||||
bind:this={filterComponent}
|
applyFilters={(detail) => {
|
||||||
on:update-filters={({ detail }) => {
|
|
||||||
jobFilters = [...detail.filters, { user: { eq: user.username } }];
|
jobFilters = [...detail.filters, { user: { eq: user.username } }];
|
||||||
selectedCluster = jobFilters[0]?.cluster
|
selectedCluster = jobFilters[0]?.cluster
|
||||||
? jobFilters[0].cluster.eq
|
? jobFilters[0].cluster.eq
|
||||||
@ -173,11 +192,11 @@
|
|||||||
</Col>
|
</Col>
|
||||||
<Col class="mb-2 mb-lg-0">
|
<Col class="mb-2 mb-lg-0">
|
||||||
<TextFilter
|
<TextFilter
|
||||||
on:set-filter={({ detail }) => filterComponent.updateFilters(detail)}
|
setFilter={(filter) => filterComponent.updateFilters(filter)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col class="mb-1 mb-lg-0">
|
<Col class="mb-1 mb-lg-0">
|
||||||
<Refresher on:refresh={() => {
|
<Refresher onRefresh={() => {
|
||||||
jobList.refreshJobs()
|
jobList.refreshJobs()
|
||||||
jobList.refreshAllMetrics()
|
jobList.refreshAllMetrics()
|
||||||
}} />
|
}} />
|
||||||
@ -269,7 +288,7 @@
|
|||||||
outline
|
outline
|
||||||
color="secondary"
|
color="secondary"
|
||||||
class="w-100"
|
class="w-100"
|
||||||
on:click={() => (isHistogramSelectionOpen = true)}
|
onclick={() => (isHistogramSelectionOpen = true)}
|
||||||
>
|
>
|
||||||
<Icon name="bar-chart-line" /> Select Histograms
|
<Icon name="bar-chart-line" /> Select Histograms
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -103,6 +103,7 @@
|
|||||||
setFilter({ cluster: pendingCluster, partition: pendingPartition });
|
setFilter({ cluster: pendingCluster, partition: pendingPartition });
|
||||||
}}>Close & Apply</Button
|
}}>Close & Apply</Button
|
||||||
>
|
>
|
||||||
|
{#if !disableClusterSelection}
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
@ -112,6 +113,7 @@
|
|||||||
setFilter({ cluster: pendingCluster, partition: pendingPartition})
|
setFilter({ cluster: pendingCluster, partition: pendingPartition})
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
|
{/if}
|
||||||
<Button onclick={() => (isOpen = false)}>Close</Button>
|
<Button onclick={() => (isOpen = false)}>Close</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user