cc-backend/web/frontend/src/List.root.svelte

209 lines
6.6 KiB
Svelte
Raw Normal View History

<!--
2022-06-22 11:20:57 +02:00
@component List of users or projects
-->
<script>
import { onMount } from "svelte";
import { init } from "./utils.js";
import { Row, Col, Button, Icon, Table,
Card, Spinner, InputGroup, Input, } from "sveltestrap";
import Filters from "./filters/Filters.svelte";
import { queryStore, gql, getContextClient } from "@urql/svelte";
import { scramble, scrambleNames } from "./joblist/JobInfo.svelte";
2023-05-04 09:19:43 +02:00
import { UniqueInputFieldNamesRule } from "graphql";
2022-06-22 11:20:57 +02:00
const {} = init();
2022-06-22 11:20:57 +02:00
export let type;
export let filterPresets;
2022-06-22 11:20:57 +02:00
console.assert(
type == "USER" || type == "PROJECT",
"Invalid list type provided!"
);
2022-06-22 11:20:57 +02:00
2023-05-04 09:19:43 +02:00
let filters;
$: stats = queryStore({
client: getContextClient(),
query: gql`
query($filter: [JobFilter!]!) {
2022-06-22 11:20:57 +02:00
rows: jobsStatistics(filter: $filter, groupBy: ${type}) {
id
name
2022-06-22 11:20:57 +02:00
totalJobs
totalWalltime
totalCoreHours
}
}`,
2023-05-04 09:19:43 +02:00
variables: { filters },
pause: true
});
2022-06-22 11:20:57 +02:00
let nameFilter = "";
let sorting = { field: "totalJobs", direction: "down" };
2022-06-22 11:20:57 +02:00
function changeSorting(event, field) {
let target = event.target;
while (target.tagName != "BUTTON") target = target.parentElement;
2022-06-22 11:20:57 +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) {
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]
: (a, b) => b[sorting.field] - a[sorting.field];
return stats.filter((u) => u.id.includes(nameFilter)).sort(cmp);
2022-06-22 11:20:57 +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>
<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}
{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-04 09:19:43 +02:00
filters = detail.filters;
stats.resume();
}}
/>
2022-06-22 11:20:57 +02:00
</Col>
</Row>
<Table>
<thead>
<tr>
<th scope="col">
{({ USER: "Username", PROJECT: "Project Name" })[type]}
<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>
{#if type == "USER"}
<th scope="col">
Name
<Button
color={sorting.field == "name" ? "primary" : "light"}
size="sm"
on:click={(e) => changeSorting(e, "name")}
>
<Icon name="sort-numeric-down" />
</Button>
</th>
{/if}
2022-06-22 11:20:57 +02:00
<th scope="col">
Total Jobs
<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
<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
<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>
<td colspan="4" style="text-align: center;"
><Spinner secondary /></td
>
2022-06-22 11:20:57 +02:00
</tr>
{:else if $stats.error}
<tr>
<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>
{#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>
{#if type == "USER"}
<td>{row?.name ? row.name : ""}</td>
{/if}
2022-06-22 11:20:57 +02:00
<td>{row.totalJobs}</td>
<td>{row.totalWalltime}</td>
<td>{row.totalCoreHours}</td>
</tr>
{:else}
<tr>
<td colspan="4"
><i>No {type.toLowerCase()}s/jobs found</i></td
>
2022-06-22 11:20:57 +02:00
</tr>
{/each}
{/if}
</tbody>
</Table>