mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-09-11 19:22:59 +02:00
Finish prototype implementation of nodelist view
This commit is contained in:
@@ -7,146 +7,159 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Badge, Icon } from "@sveltestrap/sveltestrap";
|
||||
import { scrambleNames, scramble } from "../../generic/utils.js";
|
||||
import Tag from "../../generic/helper/Tag.svelte";
|
||||
import TagManagement from "../../generic/helper/TagManagement.svelte";
|
||||
import {
|
||||
Spinner,
|
||||
Icon,
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupText, } from "@sveltestrap/sveltestrap";
|
||||
import {
|
||||
queryStore,
|
||||
gql,
|
||||
getContextClient,
|
||||
} from "@urql/svelte";
|
||||
|
||||
export let job;
|
||||
export let jobTags = job.tags;
|
||||
export let showTagedit = false;
|
||||
export let username = null;
|
||||
export let authlevel= null;
|
||||
export let roles = null;
|
||||
export let cluster;
|
||||
export let subCluster
|
||||
export let hostname;
|
||||
|
||||
function formatDuration(duration) {
|
||||
const hours = Math.floor(duration / 3600);
|
||||
duration -= hours * 3600;
|
||||
const minutes = Math.floor(duration / 60);
|
||||
duration -= minutes * 60;
|
||||
const seconds = duration;
|
||||
return `${hours}:${("0" + minutes).slice(-2)}:${("0" + seconds).slice(-2)}`;
|
||||
}
|
||||
const client = getContextClient();
|
||||
const paging = { itemsPerPage: 50, page: 1 };
|
||||
const sorting = { field: "startTime", type: "col", order: "DESC" };
|
||||
const filter = [
|
||||
{ cluster: { eq: cluster } },
|
||||
{ node: { contains: hostname } },
|
||||
{ state: ["running"] },
|
||||
];
|
||||
|
||||
function getStateColor(state) {
|
||||
switch (state) {
|
||||
case "running":
|
||||
return "success";
|
||||
case "completed":
|
||||
return "primary";
|
||||
default:
|
||||
return "danger";
|
||||
const nodeJobsQuery = gql`
|
||||
query (
|
||||
$filter: [JobFilter!]!
|
||||
$sorting: OrderByInput!
|
||||
$paging: PageRequest!
|
||||
) {
|
||||
jobs(filter: $filter, order: $sorting, page: $paging) {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
$: nodeJobsData = queryStore({
|
||||
client: client,
|
||||
query: nodeJobsQuery,
|
||||
variables: { paging, sorting, filter },
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p class="mb-2">
|
||||
<span class="fw-bold"
|
||||
><a href="/monitoring/job/{job.id}" target="_blank">{job.jobId}</a>
|
||||
({job.cluster})</span
|
||||
>
|
||||
{#if job.metaData?.jobName}
|
||||
<br />
|
||||
{#if job.metaData?.jobName.length <= 25}
|
||||
<div>{job.metaData.jobName}</div>
|
||||
{:else}
|
||||
<div
|
||||
class="truncate"
|
||||
style="cursor:help; width:230px;"
|
||||
title={job.metaData.jobName}
|
||||
>
|
||||
{job.metaData.jobName}
|
||||
</div>
|
||||
{/if}
|
||||
<Card class="pb-2">
|
||||
<CardHeader class="d-inline-flex justify-content-between align-items-end">
|
||||
<div>
|
||||
<h5 class="mb-0">
|
||||
Node
|
||||
<a href="/monitoring/node/{cluster}/{hostname}" target="_blank">
|
||||
{hostname}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="text-capitalize">
|
||||
<h6 class="mb-0">
|
||||
{cluster} {subCluster}
|
||||
</h6>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
{#if $nodeJobsData.fetching}
|
||||
<Spinner />
|
||||
{:else if $nodeJobsData.data}
|
||||
<p>
|
||||
{#if $nodeJobsData.data.jobs.count > 0}
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
<Icon name="circle-fill"/>
|
||||
</InputGroupText>
|
||||
<InputGroupText>
|
||||
Status
|
||||
</InputGroupText>
|
||||
<Button color="success" disabled>
|
||||
Allocated
|
||||
</Button>
|
||||
</InputGroup>
|
||||
{:else}
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
<Icon name="circle"/>
|
||||
</InputGroupText>
|
||||
<InputGroupText>
|
||||
Status
|
||||
</InputGroupText>
|
||||
<Button color="secondary" disabled>
|
||||
Idle
|
||||
</Button>
|
||||
</InputGroup>
|
||||
{/if}
|
||||
</p>
|
||||
<hr class="mt-0 mb-3"/>
|
||||
<p>
|
||||
{#if $nodeJobsData.data.jobs.count > 0}
|
||||
<InputGroup class="justify-content-between">
|
||||
<InputGroupText>
|
||||
<Icon name="activity"/>
|
||||
</InputGroupText>
|
||||
<InputGroupText>
|
||||
Activity
|
||||
</InputGroupText>
|
||||
<Input class="flex-grow-1" style="background-color: white;" type="text" value="{$nodeJobsData.data.jobs.count} Jobs" disabled />
|
||||
<a title="Show jobs running on this node" href="/monitoring/jobs/?cluster={cluster}&state=running&node={hostname}" target="_blank" class="btn btn-outline-primary" role="button" aria-disabled="true" >
|
||||
<Icon name="view-list" />
|
||||
Show List
|
||||
</a>
|
||||
</InputGroup>
|
||||
{:else}
|
||||
<InputGroup class="justify-content-between">
|
||||
<InputGroupText>
|
||||
<Icon name="activity" />
|
||||
</InputGroupText>
|
||||
<InputGroupText>
|
||||
Activity
|
||||
</InputGroupText>
|
||||
<Input class="flex-grow-1" type="text" style="background-color: white;" value="No running jobs." disabled />
|
||||
</InputGroup>
|
||||
{/if}
|
||||
</p>
|
||||
<p>
|
||||
<InputGroup class="justify-content-between">
|
||||
<InputGroupText>
|
||||
<Icon name="people"/>
|
||||
</InputGroupText>
|
||||
<InputGroupText class="flex-fill">
|
||||
Users on Node
|
||||
</InputGroupText>
|
||||
<a title="Show jobs running on this node" href="/monitoring/users/?cluster={cluster}&state=running&node={hostname}" target="_blank" class="btn btn-outline-primary" role="button" aria-disabled="true" >
|
||||
<Icon name="view-list" />
|
||||
Show List
|
||||
</a>
|
||||
</InputGroup>
|
||||
</p>
|
||||
<p>
|
||||
<InputGroup class="justify-content-between">
|
||||
<InputGroupText>
|
||||
<Icon name="journals"/>
|
||||
</InputGroupText>
|
||||
<InputGroupText class="flex-fill">
|
||||
Projects on Node
|
||||
</InputGroupText>
|
||||
<a title="Show projects active on this node" href="/monitoring/projects/?cluster={cluster}&state=running&node={hostname}" target="_blank" class="btn btn-outline-primary" role="button" aria-disabled="true" >
|
||||
<Icon name="view-list" />
|
||||
Show List
|
||||
</a>
|
||||
</InputGroup>
|
||||
</p>
|
||||
{/if}
|
||||
{#if job.arrayJobId}
|
||||
Array Job: <a
|
||||
href="/monitoring/jobs/?arrayJobId={job.arrayJobId}&cluster={job.cluster}"
|
||||
target="_blank">#{job.arrayJobId}</a
|
||||
>
|
||||
{/if}
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<p class="mb-2">
|
||||
<Icon name="person-fill" />
|
||||
<a class="fst-italic" href="/monitoring/user/{job.user}" target="_blank">
|
||||
{scrambleNames ? scramble(job.user) : job.user}
|
||||
</a>
|
||||
{#if job.userData && job.userData.name}
|
||||
({scrambleNames ? scramble(job.userData.name) : job.userData.name})
|
||||
{/if}
|
||||
{#if job.project && job.project != "no project"}
|
||||
<br />
|
||||
<Icon name="people-fill" />
|
||||
<a
|
||||
class="fst-italic"
|
||||
href="/monitoring/jobs/?project={job.project}&projectMatch=eq"
|
||||
target="_blank"
|
||||
>
|
||||
{scrambleNames ? scramble(job.project) : job.project}
|
||||
</a>
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<p class="mb-2">
|
||||
{#if job.numNodes == 1}
|
||||
{job.resources[0].hostname}
|
||||
{:else}
|
||||
{job.numNodes}
|
||||
{/if}
|
||||
<Icon name="pc-horizontal" />
|
||||
{#if job.exclusive != 1}
|
||||
(shared)
|
||||
{/if}
|
||||
{#if job.numAcc > 0}
|
||||
, {job.numAcc} <Icon name="gpu-card" />
|
||||
{/if}
|
||||
{#if job.numHWThreads > 0}
|
||||
, {job.numHWThreads} <Icon name="cpu" />
|
||||
{/if}
|
||||
<br />
|
||||
{job.subCluster}
|
||||
</p>
|
||||
|
||||
<p class="mb-2">
|
||||
Start: <span class="fw-bold"
|
||||
>{new Date(job.startTime).toLocaleString()}</span
|
||||
>
|
||||
<br />
|
||||
Duration: <span class="fw-bold">{formatDuration(job.duration)}</span>
|
||||
<Badge color={getStateColor(job.state)}>{job.state}</Badge>
|
||||
{#if job.walltime}
|
||||
<br />
|
||||
Walltime: <span class="fw-bold">{formatDuration(job.walltime)}</span>
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
{#if showTagedit}
|
||||
<hr class="mt-0 mb-2"/>
|
||||
<p class="mb-1">
|
||||
<TagManagement bind:jobTags {job} {username} {authlevel} {roles} renderModal/> :
|
||||
{#if jobTags?.length > 0}
|
||||
{#each jobTags as tag}
|
||||
<Tag {tag}/>
|
||||
{/each}
|
||||
{:else}
|
||||
<span style="font-size: 0.9rem; background-color: lightgray;" class="my-1 badge text-dark">No Tags</span>
|
||||
{/if}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="mb-1">
|
||||
{#each jobTags as tag}
|
||||
<Tag {tag} />
|
||||
{/each}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user