mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-04-05 11:45:55 +02:00
191 lines
6.3 KiB
Svelte
191 lines
6.3 KiB
Svelte
<!--
|
|
@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>
|
|
import { operationStore, query, mutation } 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 }
|
|
|
|
const jobs = operationStore(`
|
|
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,
|
|
SMT, exclusive, partition, arrayJobId,
|
|
monitoringStatus, state,
|
|
tags { id, type, name }
|
|
userData { name }
|
|
metaData
|
|
}
|
|
count
|
|
}
|
|
}`, {
|
|
paging,
|
|
sorting,
|
|
filter: []
|
|
}, {
|
|
pause: true
|
|
})
|
|
|
|
const updateConfiguration = mutation({
|
|
query: `mutation($name: String!, $value: String!) {
|
|
updateConfiguration(name: $name, value: $value)
|
|
}`
|
|
})
|
|
|
|
$: $jobs.variables = { ...$jobs.variables, sorting, paging }
|
|
$: matchedJobs = $jobs.data != null ? $jobs.data.jobs.count : 0
|
|
|
|
// (Re-)query and optionally set new filters.
|
|
export function update(filters) {
|
|
if (filters != null) {
|
|
let minRunningFor = ccconfig.plot_list_hideShortRunningJobs
|
|
if (minRunningFor && minRunningFor > 0) {
|
|
filters.push({ minRunningFor })
|
|
}
|
|
|
|
$jobs.variables.filter = filters
|
|
console.log('filters:', ...filters.map(f => Object.entries(f)).flat(2))
|
|
}
|
|
|
|
page = 1
|
|
$jobs.variables.paging = paging = { page, itemsPerPage };
|
|
$jobs.context.pause = false
|
|
$jobs.reexecute({ requestPolicy: 'network-only' })
|
|
}
|
|
|
|
query(jobs)
|
|
|
|
let tableWidth = null
|
|
let jobInfoColumnWidth = 250
|
|
$: plotWidth = Math.floor((tableWidth - jobInfoColumnWidth) / metrics.length - 10)
|
|
|
|
let headerPaddingTop = 0
|
|
stickyHeader('.cc-table-wrapper > table.table >thead > tr > th.position-sticky:nth-child(1)', (x) => (headerPaddingTop = x))
|
|
</script>
|
|
|
|
<Row>
|
|
<div class="col cc-table-wrapper" bind:clientWidth={tableWidth}>
|
|
<Table cellspacing="0px" cellpadding="0px">
|
|
<thead>
|
|
<tr>
|
|
<th class="position-sticky top-0" scope="col" style="width: {jobInfoColumnWidth}px; padding-top: {headerPaddingTop}px">
|
|
Job Info
|
|
</th>
|
|
{#each metrics as metric (metric)}
|
|
<th class="position-sticky top-0 text-center" scope="col" style="width: {plotWidth}px; padding-top: {headerPaddingTop}px">
|
|
{metric}
|
|
{#if $initialized}
|
|
({clusters
|
|
.map(cluster => cluster.metricConfig.find(m => m.name == metric))
|
|
.filter(m => m != null).map(m => m.unit)
|
|
.reduce((arr, unit) => arr.includes(unit) ? arr : [...arr, unit], [])
|
|
.join(', ')})
|
|
{/if}
|
|
</th>
|
|
{/each}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#if $jobs.error}
|
|
<tr>
|
|
<td colspan="{metrics.length + 1}">
|
|
<Card body color="danger" class="mb-3"><h2>{$jobs.error.message}</h2></Card>
|
|
</td>
|
|
</tr>
|
|
{:else if $jobs.fetching || !$jobs.data}
|
|
<tr>
|
|
<td colspan="{metrics.length + 1}">
|
|
<Spinner secondary />
|
|
</td>
|
|
</tr>
|
|
{:else if $jobs.data && $initialized}
|
|
{#each $jobs.data.jobs.items as job (job)}
|
|
<JobListRow
|
|
job={job}
|
|
metrics={metrics}
|
|
plotWidth={plotWidth} />
|
|
{:else}
|
|
<tr>
|
|
<td colspan="{metrics.length + 1}">
|
|
No jobs found
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
{/if}
|
|
</tbody>
|
|
</Table>
|
|
</div>
|
|
</Row>
|
|
|
|
<Pagination
|
|
bind:page={page}
|
|
{itemsPerPage}
|
|
itemText="Jobs"
|
|
totalItems={matchedJobs}
|
|
on:update={({ detail }) => {
|
|
if (detail.itemsPerPage != itemsPerPage) {
|
|
itemsPerPage = detail.itemsPerPage
|
|
updateConfiguration({
|
|
name: "plot_list_jobsPerPage",
|
|
value: itemsPerPage.toString()
|
|
}).then(res => {
|
|
if (res.error)
|
|
console.error(res.error);
|
|
})
|
|
}
|
|
|
|
paging = { itemsPerPage: detail.itemsPerPage, page: detail.page }
|
|
}} />
|
|
|
|
<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>
|