2023-01-27 18:36:58 +01:00
|
|
|
<!--
|
2022-06-22 11:20:57 +02:00
|
|
|
@component List of users or projects
|
|
|
|
-->
|
|
|
|
<script>
|
2023-05-03 16:41:17 +02:00
|
|
|
import { onMount } from "svelte";
|
|
|
|
import { init } from "./utils.js";
|
2023-05-05 10:07:12 +02:00
|
|
|
import {
|
|
|
|
Row,
|
|
|
|
Col,
|
|
|
|
Button,
|
|
|
|
Icon,
|
|
|
|
Table,
|
|
|
|
Card,
|
|
|
|
Spinner,
|
|
|
|
InputGroup,
|
|
|
|
Input,
|
|
|
|
} from "sveltestrap";
|
2023-05-03 16:41:17 +02:00
|
|
|
import Filters from "./filters/Filters.svelte";
|
|
|
|
import { queryStore, gql, getContextClient } from "@urql/svelte";
|
|
|
|
import { scramble, scrambleNames } from "./joblist/JobInfo.svelte";
|
2022-06-22 11:20:57 +02:00
|
|
|
|
2023-05-03 16:41:17 +02:00
|
|
|
const {} = init();
|
2022-06-22 11:20:57 +02:00
|
|
|
|
2023-05-03 16:41:17 +02:00
|
|
|
export let type;
|
|
|
|
export let filterPresets;
|
2022-06-22 11:20:57 +02:00
|
|
|
|
2023-05-03 16:41:17 +02:00
|
|
|
console.assert(
|
|
|
|
type == "USER" || type == "PROJECT",
|
|
|
|
"Invalid list type provided!"
|
|
|
|
);
|
2022-06-22 11:20:57 +02:00
|
|
|
|
2023-05-09 15:01:56 +02:00
|
|
|
const client = getContextClient();
|
|
|
|
$: stats = queryStore({
|
2023-05-12 11:19:37 +02:00
|
|
|
client: client,
|
|
|
|
query: gql`
|
|
|
|
query($filters: [JobFilter!]!) {
|
|
|
|
rows: jobsStatistics(filter: $filters, groupBy: ${type}) {
|
|
|
|
id
|
|
|
|
name
|
|
|
|
totalJobs
|
|
|
|
totalWalltime
|
|
|
|
totalCoreHours
|
|
|
|
}
|
|
|
|
}`,
|
|
|
|
variables: { filters }
|
2023-05-03 16:41:17 +02:00
|
|
|
});
|
2022-06-22 11:20:57 +02:00
|
|
|
|
2023-05-04 11:29:53 +02:00
|
|
|
let filters;
|
2023-05-03 16:41:17 +02:00
|
|
|
let nameFilter = "";
|
|
|
|
let sorting = { field: "totalJobs", direction: "down" };
|
2022-06-22 11:20:57 +02:00
|
|
|
|
|
|
|
function changeSorting(event, field) {
|
2023-05-03 16:41:17 +02:00
|
|
|
let target = event.target;
|
|
|
|
while (target.tagName != "BUTTON") target = target.parentElement;
|
2022-06-22 11:20:57 +02:00
|
|
|
|
2023-05-03 16:41:17 +02:00
|
|
|
let direction = target.children[0].className.includes("up")
|
|
|
|
? "down"
|
|
|
|
: "up";
|
|
|
|
target.children[0].className = `bi-sort-numeric-${direction}`;
|
|
|
|
sorting = { field, direction };
|
2022-06-22 11:20:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function sort(stats, sorting, nameFilter) {
|
2023-05-03 16:41:17 +02:00
|
|
|
const cmp =
|
|
|
|
sorting.field == "id"
|
|
|
|
? sorting.direction == "up"
|
|
|
|
? (a, b) => a.id < b.id
|
|
|
|
: (a, b) => a.id > b.id
|
|
|
|
: sorting.direction == "up"
|
2022-06-22 11:20:57 +02:00
|
|
|
? (a, b) => a[sorting.field] - b[sorting.field]
|
2023-05-03 16:41:17 +02:00
|
|
|
: (a, b) => b[sorting.field] - a[sorting.field];
|
2023-01-27 18:36:58 +01:00
|
|
|
|
2023-05-03 16:41:17 +02:00
|
|
|
return stats.filter((u) => u.id.includes(nameFilter)).sort(cmp);
|
2022-06-22 11:20:57 +02:00
|
|
|
}
|
|
|
|
|
2023-05-03 16:41:17 +02:00
|
|
|
onMount(() => filters.update());
|
2022-06-22 11:20:57 +02:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<Row>
|
|
|
|
<Col xs="auto">
|
|
|
|
<InputGroup>
|
|
|
|
<Button disabled outline>
|
|
|
|
Search {type.toLowerCase()}s
|
|
|
|
</Button>
|
2023-05-03 16:41:17 +02:00
|
|
|
<Input
|
|
|
|
bind:value={nameFilter}
|
|
|
|
placeholder="Filter by {{
|
|
|
|
USER: 'username',
|
|
|
|
PROJECT: 'project',
|
|
|
|
}[type]}"
|
|
|
|
/>
|
2022-06-22 11:20:57 +02:00
|
|
|
</InputGroup>
|
|
|
|
</Col>
|
|
|
|
<Col xs="auto">
|
|
|
|
<Filters
|
|
|
|
bind:this={filters}
|
2023-05-03 16:41:17 +02:00
|
|
|
{filterPresets}
|
2022-06-22 11:20:57 +02:00
|
|
|
startTimeQuickSelect={true}
|
|
|
|
menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up"
|
|
|
|
on:update={({ detail }) => {
|
2023-05-09 15:01:56 +02:00
|
|
|
filters = detail.filters;
|
2023-05-03 16:41:17 +02:00
|
|
|
}}
|
|
|
|
/>
|
2022-06-22 11:20:57 +02:00
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
<Table>
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th scope="col">
|
2023-05-05 10:07:12 +02:00
|
|
|
<!-- {({ -->
|
|
|
|
<!-- USER: "Username", -->
|
|
|
|
<!-- PROJECT: "Project Name", -->
|
|
|
|
<!-- })[type]} -->
|
2023-05-03 16:41:17 +02:00
|
|
|
<Button
|
|
|
|
color={sorting.field == "id" ? "primary" : "light"}
|
|
|
|
size="sm"
|
|
|
|
on:click={(e) => changeSorting(e, "id")}
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
<Icon name="sort-numeric-down" />
|
|
|
|
</Button>
|
|
|
|
</th>
|
2023-05-03 16:41:17 +02:00
|
|
|
{#if type == "USER"}
|
2023-02-17 10:45:27 +01:00
|
|
|
<th scope="col">
|
|
|
|
Name
|
2023-05-03 16:41:17 +02:00
|
|
|
<Button
|
|
|
|
color={sorting.field == "name" ? "primary" : "light"}
|
|
|
|
size="sm"
|
|
|
|
on:click={(e) => changeSorting(e, "name")}
|
|
|
|
>
|
2023-02-17 10:45:27 +01:00
|
|
|
<Icon name="sort-numeric-down" />
|
|
|
|
</Button>
|
|
|
|
</th>
|
|
|
|
{/if}
|
2022-06-22 11:20:57 +02:00
|
|
|
<th scope="col">
|
|
|
|
Total Jobs
|
2023-05-03 16:41:17 +02:00
|
|
|
<Button
|
|
|
|
color={sorting.field == "totalJobs" ? "primary" : "light"}
|
|
|
|
size="sm"
|
|
|
|
on:click={(e) => changeSorting(e, "totalJobs")}
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
<Icon name="sort-numeric-down" />
|
|
|
|
</Button>
|
|
|
|
</th>
|
|
|
|
<th scope="col">
|
|
|
|
Total Walltime
|
2023-05-03 16:41:17 +02:00
|
|
|
<Button
|
|
|
|
color={sorting.field == "totalWalltime"
|
|
|
|
? "primary"
|
|
|
|
: "light"}
|
|
|
|
size="sm"
|
|
|
|
on:click={(e) => changeSorting(e, "totalWalltime")}
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
<Icon name="sort-numeric-down" />
|
|
|
|
</Button>
|
|
|
|
</th>
|
|
|
|
<th scope="col">
|
|
|
|
Total Core Hours
|
2023-05-03 16:41:17 +02:00
|
|
|
<Button
|
|
|
|
color={sorting.field == "totalCoreHours"
|
|
|
|
? "primary"
|
|
|
|
: "light"}
|
|
|
|
size="sm"
|
|
|
|
on:click={(e) => changeSorting(e, "totalCoreHours")}
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
<Icon name="sort-numeric-down" />
|
|
|
|
</Button>
|
|
|
|
</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
{#if $stats.fetching}
|
|
|
|
<tr>
|
2023-05-03 16:41:17 +02:00
|
|
|
<td colspan="4" style="text-align: center;"
|
|
|
|
><Spinner secondary /></td
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
</tr>
|
|
|
|
{:else if $stats.error}
|
|
|
|
<tr>
|
2023-05-03 16:41:17 +02:00
|
|
|
<td colspan="4"
|
|
|
|
><Card body color="danger" class="mb-3"
|
|
|
|
>{$stats.error.message}</Card
|
|
|
|
></td
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
</tr>
|
|
|
|
{:else if $stats.data}
|
|
|
|
{#each sort($stats.data.rows, sorting, nameFilter) as row (row.id)}
|
|
|
|
<tr>
|
|
|
|
<td>
|
2023-05-03 16:41:17 +02:00
|
|
|
{#if type == "USER"}
|
|
|
|
<a href="/monitoring/user/{row.id}"
|
|
|
|
>{scrambleNames ? scramble(row.id) : row.id}</a
|
|
|
|
>
|
|
|
|
{:else if type == "PROJECT"}
|
|
|
|
<a href="/monitoring/jobs/?project={row.id}"
|
|
|
|
>{row.id}</a
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
{:else}
|
|
|
|
{row.id}
|
|
|
|
{/if}
|
|
|
|
</td>
|
2023-05-03 16:41:17 +02:00
|
|
|
{#if type == "USER"}
|
|
|
|
<td>{row?.name ? row.name : ""}</td>
|
2023-02-17 10:45:27 +01:00
|
|
|
{/if}
|
2022-06-22 11:20:57 +02:00
|
|
|
<td>{row.totalJobs}</td>
|
|
|
|
<td>{row.totalWalltime}</td>
|
|
|
|
<td>{row.totalCoreHours}</td>
|
|
|
|
</tr>
|
|
|
|
{:else}
|
|
|
|
<tr>
|
2023-05-03 16:41:17 +02:00
|
|
|
<td colspan="4"
|
|
|
|
><i>No {type.toLowerCase()}s/jobs found</i></td
|
|
|
|
>
|
2022-06-22 11:20:57 +02:00
|
|
|
</tr>
|
|
|
|
{/each}
|
|
|
|
{/if}
|
|
|
|
</tbody>
|
2023-01-27 18:36:58 +01:00
|
|
|
</Table>
|