cc-backend/web/frontend/src/joblist/JobList.svelte

273 lines
8.5 KiB
Svelte
Raw Normal View History

2022-06-22 11:20:57 +02:00
<!--
@component
Properties:
- metrics: [String] (can change from outside)
- sorting: { field: String, order: "DESC" | "ASC" } (can change from outside)
- matchedJobs: Number (changes from inside)
Functions:
- update(filters?: [JobFilter])
-->
<script>
2023-05-05 10:07:12 +02:00
import {
queryStore,
gql,
getContextClient,
mutationStore,
} from "@urql/svelte";
import { getContext } from "svelte";
import { Row, Table, Card, Spinner } from "sveltestrap";
import Pagination from "./Pagination.svelte";
import JobListRow from "./Row.svelte";
import { stickyHeader } from "../utils.js";
const ccconfig = getContext("cc-config"),
clusters = getContext("clusters"),
initialized = getContext("initialized");
export let sorting = { field: "startTime", order: "DESC" };
export let matchedJobs = 0;
export let metrics = ccconfig.plot_list_selectedMetrics;
let itemsPerPage = ccconfig.plot_list_jobsPerPage;
let page = 1;
let paging = { itemsPerPage, page };
let filter = [];
const client = getContextClient();
const query = gql`
query (
$filter: [JobFilter!]!
$sorting: OrderByInput!
$paging: PageRequest!
) {
jobs(filter: $filter, order: $sorting, page: $paging) {
items {
id
jobId
user
project
jobName
cluster
subCluster
startTime
duration
numNodes
numHWThreads
numAcc
walltime
resources {
hostname
}
SMT
exclusive
partition
arrayJobId
monitoringStatus
state
tags {
2023-05-05 10:07:12 +02:00
id
type
name
}
userData {
name
2023-05-05 10:07:12 +02:00
}
metaData
2023-05-05 10:07:12 +02:00
}
count
2022-06-22 11:20:57 +02:00
}
}
`;
$: jobs = queryStore({
client,
query,
2023-05-05 10:07:12 +02:00
variables: { paging, sorting, filter },
});
2022-06-22 11:20:57 +02:00
const configName = 'plot_list_jobsPerPage'
let configValue = ''
$: if (configValue != '') {
mutationStore({
2023-05-05 10:07:12 +02:00
client: getContextClient(),
query: gql`
mutation ($configName: String!, $configValue: String!) {
updateConfiguration(name: $configName, value: $configValue)
2023-05-05 10:07:12 +02:00
}
`,
variables: { configName, configValue },
2023-05-05 10:07:12 +02:00
});
}
const updateConfiguration = ({ value, page }) => {
configValue = value; // Trigger mutation
paging = { itemsPerPage: value, page: page }; // Trigger reload of jobList
2023-05-05 10:07:12 +02:00
};
2022-06-22 11:20:57 +02:00
2023-05-04 11:29:53 +02:00
// $: $jobs.variables = { ...$jobs.variables, sorting, paging }
2023-05-05 10:07:12 +02:00
$: matchedJobs = $jobs.data != null ? $jobs.data.jobs.count : 0;
2022-06-22 11:20:57 +02:00
// (Re-)query and optionally set new filters.
export function update(filters) {
if (filters != null) {
2023-05-05 10:07:12 +02:00
let minRunningFor = ccconfig.plot_list_hideShortRunningJobs;
2022-06-22 11:20:57 +02:00
if (minRunningFor && minRunningFor > 0) {
2023-05-05 10:07:12 +02:00
filters.push({ minRunningFor });
2022-06-22 11:20:57 +02:00
}
2023-05-05 10:07:12 +02:00
filter = filters;
2022-06-22 11:20:57 +02:00
}
2023-05-05 10:07:12 +02:00
page = 1;
paging = paging = { page, itemsPerPage };
2022-06-22 11:20:57 +02:00
}
// Force refresh list with existing unchanged variables (== usually would not trigger reactivity)
export function refresh() {
queryStore({
client,
query,
variables: { paging, sorting, filter },
requestPolicy: 'network-only'
});
}
2023-05-05 10:07:12 +02:00
let tableWidth = null;
let jobInfoColumnWidth = 250;
$: plotWidth = Math.floor(
(tableWidth - jobInfoColumnWidth) / metrics.length - 10
);
2022-06-22 11:20:57 +02:00
2023-05-05 10:07:12 +02:00
let headerPaddingTop = 0;
stickyHeader(
".cc-table-wrapper > table.table >thead > tr > th.position-sticky:nth-child(1)",
(x) => (headerPaddingTop = x)
);
2022-06-22 11:20:57 +02:00
</script>
<Row>
<div class="col cc-table-wrapper" bind:clientWidth={tableWidth}>
<Table cellspacing="0px" cellpadding="0px">
<thead>
<tr>
2023-05-05 10:07:12 +02:00
<th
class="position-sticky top-0"
scope="col"
style="width: {jobInfoColumnWidth}px; padding-top: {headerPaddingTop}px"
>
2022-06-22 11:20:57 +02:00
Job Info
</th>
{#each metrics as metric (metric)}
2023-05-05 10:07:12 +02:00
<th
class="position-sticky top-0 text-center"
scope="col"
style="width: {plotWidth}px; padding-top: {headerPaddingTop}px"
>
2022-06-22 11:20:57 +02:00
{metric}
{#if $initialized}
({clusters
2023-05-05 10:07:12 +02:00
.map((cluster) =>
cluster.metricConfig.find(
(m) => m.name == metric
)
)
.filter((m) => m != null)
.map(
(m) =>
(m.unit?.prefix
? m.unit?.prefix
: "") +
(m.unit?.base ? m.unit?.base : "")
) // Build unitStr
.reduce(
(arr, unitStr) =>
arr.includes(unitStr)
? arr
: [...arr, unitStr],
[]
) // w/o this, output would be [unitStr, unitStr]
.join(", ")})
2022-06-22 11:20:57 +02:00
{/if}
</th>
{/each}
</tr>
</thead>
<tbody>
{#if $jobs.error}
<tr>
2023-05-05 10:07:12 +02:00
<td colspan={metrics.length + 1}>
<Card body color="danger" class="mb-3"
><h2>{$jobs.error.message}</h2></Card
>
2022-06-22 11:20:57 +02:00
</td>
</tr>
{:else if $jobs.fetching || !$jobs.data}
<tr>
2023-05-05 10:07:12 +02:00
<td colspan={metrics.length + 1}>
2022-06-22 11:20:57 +02:00
<Spinner secondary />
</td>
</tr>
{:else if $jobs.data && $initialized}
{#each $jobs.data.jobs.items as job (job)}
2023-05-05 10:07:12 +02:00
<JobListRow {job} {metrics} {plotWidth} />
2022-06-22 11:20:57 +02:00
{:else}
2023-05-05 10:07:12 +02:00
<tr>
<td colspan={metrics.length + 1}>
No jobs found
</td>
</tr>
2022-06-22 11:20:57 +02:00
{/each}
{/if}
</tbody>
</Table>
</div>
</Row>
<Pagination
2023-05-05 10:07:12 +02:00
bind:page
2022-06-22 11:20:57 +02:00
{itemsPerPage}
itemText="Jobs"
totalItems={matchedJobs}
on:update={({ detail }) => {
if (detail.itemsPerPage != itemsPerPage) {
2023-05-05 10:07:12 +02:00
itemsPerPage = detail.itemsPerPage;
2022-06-22 11:20:57 +02:00
updateConfiguration({
2023-05-05 10:07:12 +02:00
value: itemsPerPage.toString(),
page: detail.page,
})
} else {
paging = { itemsPerPage: detail.itemsPerPage, page: detail.page }
2022-06-22 11:20:57 +02:00
}
2023-05-05 10:07:12 +02:00
}}
/>
2022-06-22 11:20:57 +02:00
<style>
.cc-table-wrapper {
overflow: initial;
}
.cc-table-wrapper > :global(table) {
border-collapse: separate;
border-spacing: 0px;
table-layout: fixed;
}
.cc-table-wrapper :global(button) {
margin-bottom: 0px;
}
.cc-table-wrapper > :global(table > tbody > tr > td) {
margin: 0px;
padding-left: 5px;
padding-right: 0px;
}
th.position-sticky.top-0 {
background-color: white;
z-index: 10;
border-bottom: 1px solid black;
}
</style>