mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-12-19 13:46:17 +01:00
Merge branch 'dev' of github.com:ClusterCockpit/cc-backend into dev
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
- `isApi Bool!`: Is currently logged in user api authority
|
- `isApi Bool!`: Is currently logged in user api authority
|
||||||
- `username String!`: Empty string if auth. is disabled, otherwise the username as string
|
- `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>
|
<script>
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
isApi,
|
isApi,
|
||||||
username,
|
username,
|
||||||
ncontent,
|
ncontent,
|
||||||
|
clusters
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle class="mb-1">Admin Options</CardTitle>
|
<CardTitle class="mb-1">Admin Options</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<AdminSettings {ncontent}/>
|
<AdminSettings {ncontent} {clusters}/>
|
||||||
</Card>
|
</Card>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
Table,
|
Table,
|
||||||
Progress,
|
Progress,
|
||||||
Icon,
|
Icon,
|
||||||
|
Button
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import Roofline from "./generic/plots/Roofline.svelte";
|
import Roofline from "./generic/plots/Roofline.svelte";
|
||||||
import Pie, { colors } from "./generic/plots/Pie.svelte";
|
import Pie, { colors } from "./generic/plots/Pie.svelte";
|
||||||
@@ -353,6 +354,11 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col class="d-flex justify-content-end">
|
||||||
|
<Button outline class="mb-1" size="sm" color="light" href="/">
|
||||||
|
<Icon name="x"/>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{#if $statusQuery.fetching || $statesTimed.fetching}
|
{#if $statusQuery.fetching || $statesTimed.fetching}
|
||||||
<Row class="justify-content-center">
|
<Row class="justify-content-center">
|
||||||
@@ -503,44 +509,52 @@
|
|||||||
</Col>
|
</Col>
|
||||||
<Col> <!-- Pie Last States -->
|
<Col> <!-- Pie Last States -->
|
||||||
<Row>
|
<Row>
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
{#if refinedStateData.length > 0}
|
||||||
<div bind:clientWidth={colWidthStates}>
|
<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}
|
{#key refinedStateData}
|
||||||
<Pie
|
<Table>
|
||||||
canvasId="hpcpie-slurm"
|
<tr class="mb-2">
|
||||||
size={colWidthStates * 0.66}
|
<th></th>
|
||||||
sliceLabel="Nodes"
|
<th class="h4">State</th>
|
||||||
quantities={refinedStateData.map(
|
<th class="h4">Count</th>
|
||||||
(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>
|
</tr>
|
||||||
{/each}
|
{#each refinedStateData as sd, i}
|
||||||
</Table>
|
<tr>
|
||||||
{/key}
|
<td><Icon name="circle-fill" style="color: {colors['nodeStates'][sd.state]}; font-size: 30px;"/></td>
|
||||||
</Col>
|
<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>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ mount(Config, {
|
|||||||
isApi: isApi,
|
isApi: isApi,
|
||||||
username: username,
|
username: username,
|
||||||
ncontent: ncontent,
|
ncontent: ncontent,
|
||||||
|
clusters: hClusters.map((c) => c.name) // Defined in Header Template
|
||||||
},
|
},
|
||||||
context: new Map([
|
context: new Map([
|
||||||
['cc-config', clusterCockpitConfig],
|
['cc-config', clusterCockpitConfig],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `ncontent String`: The homepage notice content
|
- `ncontent String`: The homepage notice content
|
||||||
|
- `clusters [String]`: The available clusternames
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -17,7 +18,8 @@
|
|||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
ncontent
|
ncontent,
|
||||||
|
clusters
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init*/
|
/* Const Init*/
|
||||||
@@ -66,6 +68,6 @@
|
|||||||
<Col>
|
<Col>
|
||||||
<EditProject reloadUser={() => getUserList()} />
|
<EditProject reloadUser={() => getUserList()} />
|
||||||
</Col>
|
</Col>
|
||||||
<Options config={ccconfig}/>
|
<Options config={ccconfig} {clusters}/>
|
||||||
<NoticeEdit {ncontent}/>
|
<NoticeEdit {ncontent}/>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
<!--
|
<!--
|
||||||
@component Admin option select card
|
@component Admin option select card
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
- `clusters [String]`: The available clusternames
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte";
|
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 Init */
|
||||||
const resampleConfig = getContext("resampling");
|
const resampleConfig = getContext("resampling");
|
||||||
@@ -44,6 +52,26 @@
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</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}
|
{#if resampleConfig}
|
||||||
<Col>
|
<Col>
|
||||||
<Card class="h-100">
|
<Card class="h-100">
|
||||||
|
|||||||
@@ -997,5 +997,5 @@
|
|||||||
{#if roofData != null}
|
{#if roofData != null}
|
||||||
<div bind:this={plotWrapper} class="p-2"></div>
|
<div bind:this={plotWrapper} class="p-2"></div>
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
|
|||||||
@@ -101,10 +101,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Data Prep For uPlot
|
// 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);
|
const collectLabel = sortedData.map(d => d.state);
|
||||||
// Align Data to Timesteps, Introduces 'undefied' as placeholder, reiterate and set those to 0
|
// 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 //
|
// STACKED CHART FUNCTIONS //
|
||||||
function stack(data, omit) {
|
function stack(data, omit) {
|
||||||
@@ -344,7 +344,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Define $width Wrapper and NoData Card -->
|
<!-- Define $width Wrapper and NoData Card -->
|
||||||
{#if data && collectData[0].length > 0}
|
{#if data && collectData.length > 0}
|
||||||
<div bind:this={plotWrapper} bind:clientWidth={width}
|
<div bind:this={plotWrapper} bind:clientWidth={width}
|
||||||
style="background-color: rgba(255, 255, 255, 1.0);" class="rounded"
|
style="background-color: rgba(255, 255, 255, 1.0);" class="rounded"
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@@ -487,45 +487,51 @@
|
|||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Pie Jobs -->
|
<Col> <!-- Pie Jobs -->
|
||||||
<Row cols={{xs:1, md:2}}>
|
{#if topJobsQuery?.data?.jobsStatistics?.length > 0}
|
||||||
<Col class="p-2">
|
<Row cols={{xs:1, md:2}}>
|
||||||
<div bind:clientWidth={colWidthJobs}>
|
<Col class="p-2">
|
||||||
<h4 class="text-center">
|
<div bind:clientWidth={colWidthJobs}>
|
||||||
Top Projects: Jobs
|
<h4 class="text-center">
|
||||||
</h4>
|
Top Projects: Jobs
|
||||||
<Pie
|
</h4>
|
||||||
{useCbColors}
|
<Pie
|
||||||
canvasId="hpcpie-jobs-projects"
|
{useCbColors}
|
||||||
size={colWidthJobs * 0.75}
|
canvasId="hpcpie-jobs-projects"
|
||||||
sliceLabel={'Jobs'}
|
size={colWidthJobs * 0.75}
|
||||||
quantities={$topJobsQuery.data.jobsStatistics.map(
|
sliceLabel={'Jobs'}
|
||||||
(tp) => tp['totalJobs'],
|
quantities={$topJobsQuery.data.jobsStatistics.map(
|
||||||
)}
|
(tp) => tp['totalJobs'],
|
||||||
entities={$topJobsQuery.data.jobsStatistics.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)}
|
)}
|
||||||
/>
|
entities={$topJobsQuery.data.jobsStatistics.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)}
|
||||||
</div>
|
/>
|
||||||
</Col>
|
</div>
|
||||||
<Col class="p-2">
|
</Col>
|
||||||
<Table>
|
<Col class="p-2">
|
||||||
<tr class="mb-2">
|
<Table>
|
||||||
<th></th>
|
<tr class="mb-2">
|
||||||
<th style="padding-left: 0.5rem;">Project</th>
|
<th></th>
|
||||||
<th>Jobs</th>
|
<th style="padding-left: 0.5rem;">Project</th>
|
||||||
</tr>
|
<th>Jobs</th>
|
||||||
{#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>
|
</tr>
|
||||||
{/each}
|
{#each $topJobsQuery.data.jobsStatistics as tp, i}
|
||||||
</Table>
|
<tr>
|
||||||
</Col>
|
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td>
|
||||||
</Row>
|
<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>
|
||||||
|
|
||||||
<Col> <!-- Job Roofline -->
|
<Col> <!-- Job Roofline -->
|
||||||
|
|||||||
Reference in New Issue
Block a user