chage to adaptive row sizes for publicDash

This commit is contained in:
Christoph Kluge
2026-01-21 18:14:15 +01:00
parent 55e0456aac
commit 4459840f5f

View File

@@ -348,282 +348,288 @@
</script> </script>
<Card style="height: 98vh;"> <Row>
<CardBody class="align-content-center p-2"> <Col>
<Row> <Refresher
hideSelector
initially={60}
onRefresh={(interval) => {
from = new Date(Date.now() - 5 * 60 * 1000);
to = new Date(Date.now());
clusterFrom = new Date(Date.now() - (8 * 60 * 60 * 1000))
if (interval) stackedFrom += Math.floor(interval / 1000);
else stackedFrom += 1 // Workaround: TimeSelection not linked, just trigger new data on manual refresh
}}
/>
</Col>
</Row>
{#if $statusQuery.fetching || $statesTimed.fetching}
<Row class="justify-content-center">
<Col xs="auto">
<Spinner />
</Col>
</Row>
{:else if $statusQuery.error || $statesTimed.error}
<Row class="mb-2">
<Col class="d-flex justify-content-end">
<Button color="secondary" href="/">
<Icon name="x"/>
</Button>
</Col>
</Row>
<Row cols={{xs:1, md:2}}>
{#if $statusQuery.error}
<Col> <Col>
<Refresher <Card color="danger"><CardBody>Error Requesting Status Data: {$statusQuery.error.message}</CardBody></Card>
hideSelector
initially={60}
onRefresh={(interval) => {
from = new Date(Date.now() - 5 * 60 * 1000);
to = new Date(Date.now());
clusterFrom = new Date(Date.now() - (8 * 60 * 60 * 1000))
if (interval) stackedFrom += Math.floor(interval / 1000);
else stackedFrom += 1 // Workaround: TimeSelection not linked, just trigger new data on manual refresh
}}
/>
</Col> </Col>
</Row> {/if}
{#if $statusQuery.fetching || $statesTimed.fetching} {#if $statesTimed.error}
<Row class="justify-content-center"> <Col>
<Col xs="auto"> <Card color="danger"><CardBody>Error Requesting Node Scheduler States: {$statesTimed.error.message}</CardBody></Card>
<Spinner /> </Col>
</Col> {/if}
</Row> </Row>
{:else if $statusQuery.error || $statesTimed.error} {:else}
<Row class="mb-2"> <!-- View Supposed to be Viewed at Max Viewport Size -->
<Col class="d-flex justify-content-end"> <div class="align-content-center p-2">
<Button color="secondary" href="/"> <Row cols={{xs:1, md:2}} style="height: 24vh; margin-bottom: 1rem;">
<Icon name="x"/> <Col> <!-- General Cluster Info Card -->
</Button>
</Col>
</Row>
<Row cols={{xs:1, md:2}}>
{#if $statusQuery.error}
<Col>
<Card color="danger"><CardBody>Error Requesting Status Data: {$statusQuery.error.message}</CardBody></Card>
</Col>
{/if}
{#if $statesTimed.error}
<Col>
<Card color="danger"><CardBody>Error Requesting Node Scheduler States: {$statesTimed.error.message}</CardBody></Card>
</Col>
{/if}
</Row>
{:else}
<Row cols={{xs:1, md:2}}>
<Col> <!-- General Cluster Info Card -->
<Card class="h-100">
<CardHeader>
<Row>
<Col xs="11" class="text-center">
<h2 class="mb-0">Cluster {presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)}</h2>
</Col>
<Col xs="1" class="d-flex justify-content-end">
<Button color="light" href="/">
<Icon name="x"/>
</Button>
</Col>
</Row>
</CardHeader>
<CardBody>
<h4>CPU(s)</h4><p><strong>{[...clusterInfo?.processorTypes].join(', ')}</strong></p>
</CardBody>
</Card>
</Col>
<Col> <!-- Utilization Info Card -->
<Card class="h-100"> <Card class="h-100">
<CardBody> <CardHeader>
<Row class="mb-1"> <Row>
<Col xs={4} class="d-inline-flex align-items-center justify-content-center"> <Col xs="11" class="text-center">
<Badge color="primary" style="font-size:x-large;margin-right:0.25rem;"> <h2 class="mb-0">Cluster {presetCluster.charAt(0).toUpperCase() + presetCluster.slice(1)}</h2>
{clusterInfo?.runningJobs} </Col>
</Badge> <Col xs="1" class="d-flex justify-content-end">
<div style="font-size:large;"> <Button color="light" href="/">
Running Jobs <Icon name="x"/>
</div> </Button>
</Col> </Col>
<Col xs={4} class="d-inline-flex align-items-center justify-content-center"> </Row>
<Badge color="primary" style="font-size:x-large;margin-right:0.25rem;"> </CardHeader>
{clusterInfo?.activeUsers} <CardBody>
</Badge> <h4>CPU(s)</h4><p><strong>{[...clusterInfo?.processorTypes].join(', ')}</strong></p>
<div style="font-size:large;"> </CardBody>
Active Users </Card>
</div> </Col>
</Col>
<Col xs={4} class="d-inline-flex align-items-center justify-content-center"> <Col> <!-- Utilization Info Card -->
<Badge color="primary" style="font-size:x-large;margin-right:0.25rem;"> <Card class="h-100">
{clusterInfo?.allocatedNodes} <CardBody>
</Badge> <Row class="mb-1">
<div style="font-size:large;"> <Col xs={4} class="d-inline-flex align-items-center justify-content-center">
Active Nodes <Badge color="primary" style="font-size:x-large;margin-right:0.25rem;">
</div> {clusterInfo?.runningJobs}
</Col> </Badge>
</Row> <div style="font-size:large;">
<Row class="mt-1 mb-2"> Running Jobs
</div>
</Col>
<Col xs={4} class="d-inline-flex align-items-center justify-content-center">
<Badge color="primary" style="font-size:x-large;margin-right:0.25rem;">
{clusterInfo?.activeUsers}
</Badge>
<div style="font-size:large;">
Active Users
</div>
</Col>
<Col xs={4} class="d-inline-flex align-items-center justify-content-center">
<Badge color="primary" style="font-size:x-large;margin-right:0.25rem;">
{clusterInfo?.allocatedNodes}
</Badge>
<div style="font-size:large;">
Active Nodes
</div>
</Col>
</Row>
<Row class="mt-1 mb-2">
<Col xs={4} class="d-inline-flex align-items-center justify-content-center">
<Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;">
{clusterInfo?.flopRate} {clusterInfo?.flopRateUnit}
</Badge>
<div style="font-size:large;">
Total Flop Rate
</div>
</Col>
<Col xs={4} class="d-inline-flex align-items-center justify-content-center">
<Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;">
{clusterInfo?.memBwRate} {clusterInfo?.memBwRateUnit}
</Badge>
<div style="font-size:large;">
Total Memory Bandwidth
</div>
</Col>
{#if clusterInfo?.totalAccs !== 0}
<Col xs={4} class="d-inline-flex align-items-center justify-content-center"> <Col xs={4} class="d-inline-flex align-items-center justify-content-center">
<Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;"> <Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;">
{clusterInfo?.flopRate} {clusterInfo?.flopRateUnit} {clusterInfo?.gpuPwr} {clusterInfo?.gpuPwrUnit}
</Badge> </Badge>
<div style="font-size:large;"> <div style="font-size:large;">
Total Flop Rate Total GPU Power
</div> </div>
</Col> </Col>
{:else}
<Col xs={4} class="d-inline-flex align-items-center justify-content-center"> <Col xs={4} class="d-inline-flex align-items-center justify-content-center">
<Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;"> <Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;">
{clusterInfo?.memBwRate} {clusterInfo?.memBwRateUnit} {clusterInfo?.cpuPwr} {clusterInfo?.cpuPwrUnit}
</Badge> </Badge>
<div style="font-size:large;"> <div style="font-size:large;">
Total Memory Bandwidth Total CPU Power
</div> </div>
</Col> </Col>
{#if clusterInfo?.totalAccs !== 0} {/if}
<Col xs={4} class="d-inline-flex align-items-center justify-content-center"> </Row>
<Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;"> <Row class="my-1 align-items-baseline">
{clusterInfo?.gpuPwr} {clusterInfo?.gpuPwrUnit} <Col xs={2} style="font-size:large;">
</Badge> Active Cores
<div style="font-size:large;"> </Col>
Total GPU Power <Col xs={8}>
</div> <Progress multi max={clusterInfo?.totalCores} style="height:2.5rem;font-size:x-large;">
</Col> <Progress bar color="success" value={clusterInfo?.allocatedCores} title={`${clusterInfo?.allocatedCores} active`}>{formatNumber(clusterInfo?.allocatedCores)}</Progress>
{:else} <Progress bar color="light" value={clusterInfo?.idleCores} title={`${clusterInfo?.idleCores} idle`}>{formatNumber(clusterInfo?.idleCores)}</Progress>
<Col xs={4} class="d-inline-flex align-items-center justify-content-center"> </Progress>
<Badge color="secondary" style="font-size:x-large;margin-right:0.25rem;"> </Col>
{clusterInfo?.cpuPwr} {clusterInfo?.cpuPwrUnit} <Col xs={2} style="font-size:large;">
</Badge> Idle Cores
<div style="font-size:large;"> </Col>
Total CPU Power </Row>
</div> {#if clusterInfo?.totalAccs !== 0}
</Col>
{/if}
</Row>
<Row class="my-1 align-items-baseline"> <Row class="my-1 align-items-baseline">
<Col xs={2} style="font-size:large;"> <Col xs={2} style="font-size:large;">
Active Cores Active GPU
</Col> </Col>
<Col xs={8}> <Col xs={8}>
<Progress multi max={clusterInfo?.totalCores} style="height:2.5rem;font-size:x-large;"> <Progress multi max={clusterInfo?.totalAccs} style="height:2.5rem;font-size:x-large;">
<Progress bar color="success" value={clusterInfo?.allocatedCores} title={`${clusterInfo?.allocatedCores} active`}>{formatNumber(clusterInfo?.allocatedCores)}</Progress> <Progress bar color="success" value={clusterInfo?.allocatedAccs} title={`${clusterInfo?.allocatedAccs} active`}>{formatNumber(clusterInfo?.allocatedAccs)}</Progress>
<Progress bar color="light" value={clusterInfo?.idleCores} title={`${clusterInfo?.idleCores} idle`}>{formatNumber(clusterInfo?.idleCores)}</Progress> <Progress bar color="light" value={clusterInfo?.idleAccs} title={`${clusterInfo?.idleAccs} idle`}>{formatNumber(clusterInfo?.idleAccs)}</Progress>
</Progress> </Progress>
</Col> </Col>
<Col xs={2} style="font-size:large;"> <Col xs={2} style="font-size:large;">
Idle Cores Idle GPU
</Col> </Col>
</Row> </Row>
{#if clusterInfo?.totalAccs !== 0}
<Row class="my-1 align-items-baseline">
<Col xs={2} style="font-size:large;">
Active GPU
</Col>
<Col xs={8}>
<Progress multi max={clusterInfo?.totalAccs} style="height:2.5rem;font-size:x-large;">
<Progress bar color="success" value={clusterInfo?.allocatedAccs} title={`${clusterInfo?.allocatedAccs} active`}>{formatNumber(clusterInfo?.allocatedAccs)}</Progress>
<Progress bar color="light" value={clusterInfo?.idleAccs} title={`${clusterInfo?.idleAccs} idle`}>{formatNumber(clusterInfo?.idleAccs)}</Progress>
</Progress>
</Col>
<Col xs={2} style="font-size:large;">
Idle GPU
</Col>
</Row>
{/if}
</CardBody>
</Card>
</Col>
<!-- Total Cluster Metric in Time SUMS-->
<Col class="text-center">
<h5 class="mt-2 mb-0">
Cluster Utilization (
<span style="color: #0000ff;">
{`${$statusQuery?.data?.clusterMetrics?.metrics[0]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.base})`}
</span>,
<span style="color: #ff0000;">
{`${$statusQuery?.data?.clusterMetrics?.metrics[1]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.base})`}
</span>
)
</h5>
<div>
{#key $statusQuery?.data?.clusterMetrics}
<DoubleMetric
timestep={$statusQuery?.data?.clusterMetrics[0]?.timestep || 60}
numNodes={$statusQuery?.data?.clusterMetrics?.nodeCount || 0}
metricData={$statusQuery?.data?.clusterMetrics?.metrics || []}
publicMode
/>
{/key}
</div>
</Col>
<Col> <!-- Nodes Roofline -->
<div>
{#key $statusQuery?.data?.nodeMetrics}
<Roofline
colorBackground
useColors={false}
useLegend={false}
allowSizeChange
cluster={presetCluster}
subCluster={clusterInfo?.roofData ? clusterInfo.roofData : null}
roofData={transformNodesStatsToData($statusQuery?.data?.nodeMetrics)}
nodesData={transformNodesStatsToInfo($statusQuery?.data?.nodeMetrics)}
fixTitle="Node Utilization"
yMinimum={1.0}
/>
{/key}
</div>
</Col>
<Col> <!-- Pie Last States -->
<Row>
{#if refinedStateData.length > 0}
<Col class="px-3 mt-2 mt-lg-0">
<div bind:clientWidth={colWidthStates}>
{#key refinedStateData}
<Pie
canvasId="hpcpie-slurm"
size={colWidthStates * 0.66}
sliceLabel="Nodes"
quantities={refinedStateData.map(
(sd) => sd.count,
)}
entities={refinedStateData.map(
(sd) => sd.state,
)}
fixColors={refinedStateData.map(
(sd) => colors['nodeStates'][sd.state],
)}
/>
{/key}
</div>
</Col>
<Col class="px-4 py-2">
{#key refinedStateData}
<Table>
<tr class="mb-2">
<th></th>
<th class="h4">State</th>
<th class="h4">Count</th>
</tr>
{#each refinedStateData as sd, i}
<tr>
<td><Icon name="circle-fill" style="color: {colors['nodeStates'][sd.state]}; font-size: 30px;"/></td>
<td class="h5">{sd.state.charAt(0).toUpperCase() + sd.state.slice(1)}</td>
<td class="h5">{sd.count}</td>
</tr>
{/each}
</Table>
{/key}
</Col>
{:else}
<Col>
<Card body color="warning" class="mx-4 my-2"
>Cannot render state status: No state data returned for <code>Pie Chart</code></Card
>
</Col>
{/if} {/if}
</Row> </CardBody>
</Col> </Card>
</Col>
</Row>
<Col> <!-- Stacked SchedState --> <Row cols={{xs:1, md:2}} style="height: 35vh; margin-bottom: 1rem;">
<div> <!-- Total Cluster Metric in Time SUMS-->
{#key $statesTimed?.data?.nodeStatesTimed} <Col class="text-center">
<Stacked <h5 class="mt-2 mb-0">
data={$statesTimed?.data?.nodeStatesTimed} Cluster Utilization (
height={260} <span style="color: #0000ff;">
ylabel="Nodes" {`${$statusQuery?.data?.clusterMetrics?.metrics[0]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.base})`}
yunit = "#Count" </span>,
title = "Cluster Status" <span style="color: #ff0000;">
stateType = "Node" {`${$statusQuery?.data?.clusterMetrics?.metrics[1]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.base})`}
/> </span>
{/key} )
</div> </h5>
</Col> <div>
</Row> {#key $statusQuery?.data?.clusterMetrics}
{/if} <DoubleMetric
</CardBody> timestep={$statusQuery?.data?.clusterMetrics[0]?.timestep || 60}
</Card> numNodes={$statusQuery?.data?.clusterMetrics?.nodeCount || 0}
metricData={$statusQuery?.data?.clusterMetrics?.metrics || []}
publicMode
/>
{/key}
</div>
</Col>
<Col> <!-- Nodes Roofline -->
<div>
{#key $statusQuery?.data?.nodeMetrics}
<Roofline
colorBackground
useColors={false}
useLegend={false}
allowSizeChange
cluster={presetCluster}
subCluster={clusterInfo?.roofData ? clusterInfo.roofData : null}
roofData={transformNodesStatsToData($statusQuery?.data?.nodeMetrics)}
nodesData={transformNodesStatsToInfo($statusQuery?.data?.nodeMetrics)}
fixTitle="Node Utilization"
yMinimum={1.0}
height={330}
/>
{/key}
</div>
</Col>
</Row>
<Row cols={{xs:1, md:2}} style="height: 35vh;">
<Col> <!-- Pie Last States -->
<Row>
{#if refinedStateData.length > 0}
<Col class="px-3 mt-2 mt-lg-0">
<div bind:clientWidth={colWidthStates}>
{#key refinedStateData}
<Pie
canvasId="hpcpie-slurm"
size={colWidthStates * 0.66}
sliceLabel="Nodes"
quantities={refinedStateData.map(
(sd) => sd.count,
)}
entities={refinedStateData.map(
(sd) => sd.state,
)}
fixColors={refinedStateData.map(
(sd) => colors['nodeStates'][sd.state],
)}
/>
{/key}
</div>
</Col>
<Col class="px-4 py-2">
{#key refinedStateData}
<Table>
<tr class="mb-2">
<th></th>
<th class="h4">State</th>
<th class="h4">Count</th>
</tr>
{#each refinedStateData as sd, i}
<tr>
<td><Icon name="circle-fill" style="color: {colors['nodeStates'][sd.state]}; font-size: 30px;"/></td>
<td class="h5">{sd.state.charAt(0).toUpperCase() + sd.state.slice(1)}</td>
<td class="h5">{sd.count}</td>
</tr>
{/each}
</Table>
{/key}
</Col>
{:else}
<Col>
<Card body color="warning" class="mx-4 my-2"
>Cannot render state status: No state data returned for <code>Pie Chart</code></Card
>
</Col>
{/if}
</Row>
</Col>
<Col> <!-- Stacked SchedState -->
<div>
{#key $statesTimed?.data?.nodeStatesTimed}
<Stacked
data={$statesTimed?.data?.nodeStatesTimed}
height={300}
ylabel="Nodes"
yunit = "#Count"
title = "Cluster Status"
stateType = "Node"
/>
{/key}
</div>
</Col>
</Row>
</div>
{/if}