Merge branch 'dev' of github.com:ClusterCockpit/cc-backend into dev

This commit is contained in:
2025-12-17 05:46:10 +01:00
8 changed files with 137 additions and 84 deletions

View File

@@ -6,7 +6,8 @@
- `isSupport Bool!`: Is currently logged in user support authority
- `isApi Bool!`: Is currently logged in user api authority
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
- `ncontent String!`: The currently displayed message on the homescreen
- `ncontent String!`: The currently displayed message on the homescreen
- `clusters [String]`: The available clusternames
-->
<script>
@@ -22,6 +23,7 @@
isApi,
username,
ncontent,
clusters
} = $props();
</script>
@@ -30,7 +32,7 @@
<CardHeader>
<CardTitle class="mb-1">Admin Options</CardTitle>
</CardHeader>
<AdminSettings {ncontent}/>
<AdminSettings {ncontent} {clusters}/>
</Card>
{/if}

View File

@@ -30,6 +30,7 @@
Table,
Progress,
Icon,
Button
} from "@sveltestrap/sveltestrap";
import Roofline from "./generic/plots/Roofline.svelte";
import Pie, { colors } from "./generic/plots/Pie.svelte";
@@ -353,6 +354,11 @@
}}
/>
</Col>
<Col class="d-flex justify-content-end">
<Button outline class="mb-1" size="sm" color="light" href="/">
<Icon name="x"/>
</Button>
</Col>
</Row>
{#if $statusQuery.fetching || $statesTimed.fetching}
<Row class="justify-content-center">
@@ -503,44 +509,52 @@
</Col>
<Col> <!-- Pie Last States -->
<Row>
<Col class="px-3 mt-2 mt-lg-0">
<div bind:clientWidth={colWidthStates}>
{#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}
<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>
<Table>
<tr class="mb-2">
<th></th>
<th class="h4">State</th>
<th class="h4">Count</th>
</tr>
{/each}
</Table>
{/key}
</Col>
{#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>

View File

@@ -10,6 +10,7 @@ mount(Config, {
isApi: isApi,
username: username,
ncontent: ncontent,
clusters: hClusters.map((c) => c.name) // Defined in Header Template
},
context: new Map([
['cc-config', clusterCockpitConfig],

View File

@@ -3,6 +3,7 @@
Properties:
- `ncontent String`: The homepage notice content
- `clusters [String]`: The available clusternames
-->
<script>
@@ -17,7 +18,8 @@
/* Svelte 5 Props */
let {
ncontent
ncontent,
clusters
} = $props();
/* Const Init*/
@@ -66,6 +68,6 @@
<Col>
<EditProject reloadUser={() => getUserList()} />
</Col>
<Options config={ccconfig}/>
<Options config={ccconfig} {clusters}/>
<NoticeEdit {ncontent}/>
</Row>

View File

@@ -1,14 +1,22 @@
<!--
@component Admin option select card
Properties:
- `clusters [String]`: The available clusternames
-->
<script>
import { getContext, onMount } from "svelte";
import { Col, Card, CardBody, CardTitle } from "@sveltestrap/sveltestrap";
import { Row, Col, Card, CardBody, CardTitle, Button, Icon } from "@sveltestrap/sveltestrap";
/* Svelte 5 Props */
let {
clusters,
} = $props();
/*Const Init */
const resampleConfig = getContext("resampling");
/* State Init */
let scrambled = $state(false);
@@ -44,6 +52,26 @@
</Card>
</Col>
{#if clusters?.length > 0}
<Col>
<Card class="h-100">
<CardBody>
<CardTitle class="mb-3">Public Dashboard Links</CardTitle>
<Row>
{#each clusters as cluster}
<Col>
<Button color="info" class="mb-2 mb-xl-0" href={`/monitoring/dashboard/${cluster}`} target="_blank">
<Icon name="clipboard-pulse" class="mr-2"/>
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Public Dashboard
</Button>
</Col>
{/each}
</Row>
</CardBody>
</Card>
</Col>
{/if}
{#if resampleConfig}
<Col>
<Card class="h-100">

View File

@@ -997,5 +997,5 @@
{#if roofData != null}
<div bind:this={plotWrapper} class="p-2"></div>
{:else}
<Card class="mx-4" body color="warning">Cannot render roofline: No data!</Card>
<Card class="mx-4 my-2" body color="warning">Cannot render roofline: No data!</Card>
{/if}

View File

@@ -101,10 +101,10 @@
};
// Data Prep For uPlot
const sortedData = data.sort((a, b) => a.state.localeCompare(b.state));
const sortedData = data?.sort((a, b) => a.state.localeCompare(b.state)) || [];
const collectLabel = sortedData.map(d => d.state);
// Align Data to Timesteps, Introduces 'undefied' as placeholder, reiterate and set those to 0
const collectData = uPlot.join(sortedData.map(d => [d.times, d.counts])).map(d => d.map(i => i ? i : 0));
const collectData = (sortedData.length > 0) ? uPlot.join(sortedData.map(d => [d.times, d.counts])).map(d => d.map(i => i ? i : 0)) : [];
// STACKED CHART FUNCTIONS //
function stack(data, omit) {
@@ -344,7 +344,7 @@
</script>
<!-- Define $width Wrapper and NoData Card -->
{#if data && collectData[0].length > 0}
{#if data && collectData.length > 0}
<div bind:this={plotWrapper} bind:clientWidth={width}
style="background-color: rgba(255, 255, 255, 1.0);" class="rounded"
></div>

View File

@@ -487,45 +487,51 @@
</Col>
<Col> <!-- Pie Jobs -->
<Row cols={{xs:1, md:2}}>
<Col class="p-2">
<div bind:clientWidth={colWidthJobs}>
<h4 class="text-center">
Top Projects: Jobs
</h4>
<Pie
{useCbColors}
canvasId="hpcpie-jobs-projects"
size={colWidthJobs * 0.75}
sliceLabel={'Jobs'}
quantities={$topJobsQuery.data.jobsStatistics.map(
(tp) => tp['totalJobs'],
)}
entities={$topJobsQuery.data.jobsStatistics.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)}
/>
</div>
</Col>
<Col class="p-2">
<Table>
<tr class="mb-2">
<th></th>
<th style="padding-left: 0.5rem;">Project</th>
<th>Jobs</th>
</tr>
{#each $topJobsQuery.data.jobsStatistics as tp, i}
<tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td>
<td>
<a target="_blank" href="/monitoring/jobs/?cluster={presetCluster}&state=running&project={tp.id}&projectMatch=eq"
>{scrambleNames ? scramble(tp.id) : tp.id}
</a>
</td>
<td>{tp['totalJobs']}</td>
{#if topJobsQuery?.data?.jobsStatistics?.length > 0}
<Row cols={{xs:1, md:2}}>
<Col class="p-2">
<div bind:clientWidth={colWidthJobs}>
<h4 class="text-center">
Top Projects: Jobs
</h4>
<Pie
{useCbColors}
canvasId="hpcpie-jobs-projects"
size={colWidthJobs * 0.75}
sliceLabel={'Jobs'}
quantities={$topJobsQuery.data.jobsStatistics.map(
(tp) => tp['totalJobs'],
)}
entities={$topJobsQuery.data.jobsStatistics.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)}
/>
</div>
</Col>
<Col class="p-2">
<Table>
<tr class="mb-2">
<th></th>
<th style="padding-left: 0.5rem;">Project</th>
<th>Jobs</th>
</tr>
{/each}
</Table>
</Col>
</Row>
{#each $topJobsQuery.data.jobsStatistics as tp, i}
<tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td>
<td>
<a target="_blank" href="/monitoring/jobs/?cluster={presetCluster}&state=running&project={tp.id}&projectMatch=eq"
>{scrambleNames ? scramble(tp.id) : tp.id}
</a>
</td>
<td>{tp['totalJobs']}</td>
</tr>
{/each}
</Table>
</Col>
</Row>
{:else}
<Card body color="warning" class="mx-4 my-2"
>Cannot render job status: No state data returned for <code>Pie Chart</code></Card
>
{/if}
</Col>
<Col> <!-- Job Roofline -->