mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-01-23 18:09:06 +01:00
add extended legend for nodelist acc metrics, move nodelist paging select
This commit is contained in:
parent
d0580592be
commit
735988decb
@ -3,6 +3,7 @@
|
||||
|
||||
Properties:
|
||||
- `ìsAdmin Bool!`: Is currently logged in user admin authority
|
||||
- `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
|
||||
-->
|
||||
@ -10,15 +11,17 @@
|
||||
<script>
|
||||
import { Card, CardHeader, CardTitle } from "@sveltestrap/sveltestrap";
|
||||
import UserSettings from "./config/UserSettings.svelte";
|
||||
import SupportSettings from "./config/SupportSettings.svelte";
|
||||
import AdminSettings from "./config/AdminSettings.svelte";
|
||||
|
||||
export let isAdmin;
|
||||
export let isSupport;
|
||||
export let isApi;
|
||||
export let username;
|
||||
export let ncontent;
|
||||
</script>
|
||||
|
||||
{#if isAdmin == true}
|
||||
{#if isAdmin}
|
||||
<Card style="margin-bottom: 1.5em;">
|
||||
<CardHeader>
|
||||
<CardTitle class="mb-1">Admin Options</CardTitle>
|
||||
@ -27,6 +30,15 @@
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
{#if isSupport || isAdmin}
|
||||
<Card style="margin-bottom: 1.5em;">
|
||||
<CardHeader>
|
||||
<CardTitle class="mb-1">Support Options</CardTitle>
|
||||
</CardHeader>
|
||||
<SupportSettings/>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="mb-1">User Options</CardTitle>
|
||||
|
@ -9,7 +9,7 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext, createEventDispatcher } from "svelte";
|
||||
import { getContext } from "svelte";
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
@ -64,10 +64,8 @@
|
||||
let isMetricsSelectionOpen = false;
|
||||
|
||||
/*
|
||||
Note 1: Scope Selector or Auto-Scoped? -> USeful auto scoping with stats view where applicable -> CHeck with JVe
|
||||
Note 2: "Sorting" as use-case ignored for now, probably default to alphanumerical on hostnames of cluster (handled in frontend at the moment)
|
||||
Note 3: Add Idle State Filter (== No allocated Jobs) [Frontend?] : Cannot be handled by CCMS, requires secondary job query and refiltering of visible nodes
|
||||
Note 4: Resolution changes as implemented only possible for all plots generally, not for individual metrics: Result list if build from GQL result *including* metric series
|
||||
Note 1: "Sorting" as use-case ignored for now, probably default to alphanumerical on hostnames of cluster (handled in frontend at the moment)
|
||||
Note 2: Add Idle State Filter (== No allocated Jobs) [Frontend?] : Cannot be handled by CCMS, requires secondary job query and refiltering of visible nodes
|
||||
*/
|
||||
|
||||
let systemMetrics = [];
|
||||
|
@ -5,6 +5,7 @@ new Config({
|
||||
target: document.getElementById('svelte-app'),
|
||||
props: {
|
||||
isAdmin: isAdmin,
|
||||
isSupport: isSupport,
|
||||
isApi: isApi,
|
||||
username: username,
|
||||
ncontent: ncontent,
|
||||
|
13
web/frontend/src/config/SupportSettings.svelte
Normal file
13
web/frontend/src/config/SupportSettings.svelte
Normal file
@ -0,0 +1,13 @@
|
||||
<!--
|
||||
@component Support settings wrapper
|
||||
Properties: None
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import SupportOptions from "./support/SupportOptions.svelte";
|
||||
|
||||
const ccconfig = getContext("cc-config");
|
||||
</script>
|
||||
|
||||
<SupportOptions config={ccconfig}/>
|
@ -4,13 +4,8 @@
|
||||
|
||||
<script>
|
||||
import { getContext, onMount } from "svelte";
|
||||
import { Col, Card, CardBody, CardTitle, Button} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
import { Col, Card, CardBody, CardTitle } from "@sveltestrap/sveltestrap";
|
||||
|
||||
export let config;
|
||||
|
||||
let message;
|
||||
let displayMessage;
|
||||
let scrambled;
|
||||
|
||||
const resampleConfig = getContext("resampling");
|
||||
@ -28,33 +23,6 @@
|
||||
window.localStorage.removeItem("cc-scramble-names");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSettingSubmit(selector, target) {
|
||||
let form = document.querySelector(selector);
|
||||
let formData = new FormData(form);
|
||||
try {
|
||||
const res = await fetch(form.action, { method: "POST", body: formData });
|
||||
if (res.ok) {
|
||||
let text = await res.text();
|
||||
popMessage(text, target, "#048109");
|
||||
} else {
|
||||
let text = await res.text();
|
||||
throw new Error("Response Code " + res.status + "-> " + text);
|
||||
}
|
||||
} catch (err) {
|
||||
popMessage(err, target, "#d63384");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function popMessage(response, restarget, rescolor) {
|
||||
message = { msg: response, target: restarget, color: rescolor };
|
||||
displayMessage = true;
|
||||
setTimeout(function () {
|
||||
displayMessage = false;
|
||||
}, 3500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Col>
|
||||
@ -73,53 +41,6 @@
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<form
|
||||
id="node-paging-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
on:submit|preventDefault={() =>
|
||||
handleSettingSubmit("#node-paging-form", "npag")}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Node List Paging Type</div>
|
||||
{#if displayMessage && message.target == "npag"}<div
|
||||
style="margin-left: auto; font-size: 0.9em;"
|
||||
>
|
||||
<code style="color: {message.color};" out:fade
|
||||
>Update: {message.msg}</code
|
||||
>
|
||||
</div>{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="node_list_usePaging" />
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
{#if config?.node_list_usePaging}
|
||||
<input type="radio" id="nodes-true-checked" name="value" value="true" checked />
|
||||
{:else}
|
||||
<input type="radio" id="nodes-true" name="value" value="true" />
|
||||
{/if}
|
||||
<label for="true">Paging with selectable count of nodes.</label>
|
||||
</div>
|
||||
<div>
|
||||
{#if config?.node_list_usePaging}
|
||||
<input type="radio" id="nodes-false" name="value" value="false" />
|
||||
{:else}
|
||||
<input type="radio" id="nodes-false-checked" name="value" value="false" checked />
|
||||
{/if}
|
||||
<label for="false">Continuous scroll iteratively adding 10 nodes.</label>
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{#if resampleConfig}
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
|
89
web/frontend/src/config/support/SupportOptions.svelte
Normal file
89
web/frontend/src/config/support/SupportOptions.svelte
Normal file
@ -0,0 +1,89 @@
|
||||
<!--
|
||||
@component Support option select card
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Row, Col, Card, CardTitle, Button} from "@sveltestrap/sveltestrap";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
export let config;
|
||||
|
||||
let message;
|
||||
let displayMessage;
|
||||
|
||||
async function handleSettingSubmit(selector, target) {
|
||||
let form = document.querySelector(selector);
|
||||
let formData = new FormData(form);
|
||||
try {
|
||||
const res = await fetch(form.action, { method: "POST", body: formData });
|
||||
if (res.ok) {
|
||||
let text = await res.text();
|
||||
popMessage(text, target, "#048109");
|
||||
} else {
|
||||
let text = await res.text();
|
||||
throw new Error("Response Code " + res.status + "-> " + text);
|
||||
}
|
||||
} catch (err) {
|
||||
popMessage(err, target, "#d63384");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function popMessage(response, restarget, rescolor) {
|
||||
message = { msg: response, target: restarget, color: rescolor };
|
||||
displayMessage = true;
|
||||
setTimeout(function () {
|
||||
displayMessage = false;
|
||||
}, 3500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Row cols={1} class="p-2 g-2">
|
||||
<Col>
|
||||
<Card class="h-100">
|
||||
<form
|
||||
id="node-paging-form"
|
||||
method="post"
|
||||
action="/frontend/configuration/"
|
||||
class="card-body"
|
||||
on:submit|preventDefault={() =>
|
||||
handleSettingSubmit("#node-paging-form", "npag")}
|
||||
>
|
||||
<!-- Svelte 'class' directive only on DOMs directly, normal 'class="xxx"' does not work, so style-array it is. -->
|
||||
<CardTitle
|
||||
style="margin-bottom: 1em; display: flex; align-items: center;"
|
||||
>
|
||||
<div>Node List Paging Type</div>
|
||||
{#if displayMessage && message.target == "npag"}<div
|
||||
style="margin-left: auto; font-size: 0.9em;"
|
||||
>
|
||||
<code style="color: {message.color};" out:fade
|
||||
>Update: {message.msg}</code
|
||||
>
|
||||
</div>{/if}
|
||||
</CardTitle>
|
||||
<input type="hidden" name="key" value="node_list_usePaging" />
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
{#if config?.node_list_usePaging}
|
||||
<input type="radio" id="nodes-true-checked" name="value" value="true" checked />
|
||||
{:else}
|
||||
<input type="radio" id="nodes-true" name="value" value="true" />
|
||||
{/if}
|
||||
<label for="true">Paging with selectable count of nodes.</label>
|
||||
</div>
|
||||
<div>
|
||||
{#if config?.node_list_usePaging}
|
||||
<input type="radio" id="nodes-false" name="value" value="false" />
|
||||
{:else}
|
||||
<input type="radio" id="nodes-false-checked" name="value" value="false" checked />
|
||||
{/if}
|
||||
<label for="false">Continuous scroll iteratively adding 10 nodes.</label>
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary" type="submit">Submit</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
@ -138,6 +138,7 @@
|
||||
export let numaccs = 0;
|
||||
export let zoomState = null;
|
||||
export let thresholdState = null;
|
||||
export let extendedLegendData = null;
|
||||
|
||||
if (!useStatsSeries && statisticsSeries != null) useStatsSeries = true;
|
||||
|
||||
@ -191,6 +192,7 @@
|
||||
className && legendEl.classList.add(className);
|
||||
|
||||
uPlot.assign(legendEl.style, {
|
||||
minWidth: extendedLegendData ? "300px" : "100px",
|
||||
textAlign: "left",
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
@ -307,13 +309,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const plotSeries = [
|
||||
{
|
||||
label: "Runtime",
|
||||
value: (u, ts, sidx, didx) =>
|
||||
didx == null ? null : formatTime(ts, forNode),
|
||||
},
|
||||
];
|
||||
const plotData = [new Array(longestSeries)];
|
||||
if (forNode === true) {
|
||||
// Negative Timestamp Buildup
|
||||
@ -330,6 +325,15 @@
|
||||
plotData[0][j] = j * timestep;
|
||||
}
|
||||
|
||||
const plotSeries = [
|
||||
// Note: X-Legend Will not be shown as soon as Y-Axis are in extendedMode
|
||||
{
|
||||
label: "Runtime",
|
||||
value: (u, ts, sidx, didx) =>
|
||||
(didx == null) ? null : formatTime(ts, forNode),
|
||||
}
|
||||
];
|
||||
|
||||
let plotBands = undefined;
|
||||
if (useStatsSeries) {
|
||||
plotData.push(statisticsSeries.min);
|
||||
@ -366,16 +370,61 @@
|
||||
} else {
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
plotData.push(series[i].data);
|
||||
// Default
|
||||
if (!extendedLegendData) {
|
||||
plotSeries.push({
|
||||
label:
|
||||
scope === "node"
|
||||
? series[i].hostname
|
||||
: scope === "accelerator"
|
||||
? 'Acc #' + (i + 1) // series[i].id.slice(9, 14) | Too Hardware Specific
|
||||
: scope + " #" + (i + 1),
|
||||
scale: "y",
|
||||
width: lineWidth,
|
||||
stroke: lineColor(i, series.length),
|
||||
});
|
||||
}
|
||||
// Extended Legend For NodeList
|
||||
else {
|
||||
plotSeries.push({
|
||||
label:
|
||||
scope === "node"
|
||||
? series[i].hostname
|
||||
: scope === "accelerator"
|
||||
? 'Acc #' + (i + 1) // series[i].id.slice(9, 14) | Too Hardware Specific
|
||||
: scope + " #" + (i + 1),
|
||||
scale: "y",
|
||||
width: lineWidth,
|
||||
stroke: lineColor(i, series.length),
|
||||
values: (u, sidx, idx) => {
|
||||
// "i" = "sidx - 1" : sidx contains x-axis-data
|
||||
if (idx == null)
|
||||
return {
|
||||
time: '-',
|
||||
value: '-',
|
||||
user: '-',
|
||||
job: '-'
|
||||
};
|
||||
|
||||
if (series[i].id in extendedLegendData) {
|
||||
return {
|
||||
time: formatTime(plotData[0][idx], forNode),
|
||||
value: plotData[sidx][idx],
|
||||
user: extendedLegendData[series[i].id].user,
|
||||
job: extendedLegendData[series[i].id].job,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
time: formatTime(plotData[0][idx], forNode),
|
||||
value: plotData[sidx][idx],
|
||||
user: '-',
|
||||
job: '-',
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const opts = {
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
<script>
|
||||
import {
|
||||
Spinner,
|
||||
Icon,
|
||||
Button,
|
||||
Card,
|
||||
@ -18,61 +17,24 @@
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupText, } from "@sveltestrap/sveltestrap";
|
||||
import {
|
||||
queryStore,
|
||||
gql,
|
||||
getContextClient,
|
||||
} from "@urql/svelte";
|
||||
|
||||
export let cluster;
|
||||
export let subCluster
|
||||
export let hostname;
|
||||
export let dataHealth;
|
||||
|
||||
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"] },
|
||||
];
|
||||
|
||||
const nodeJobsQuery = gql`
|
||||
query (
|
||||
$filter: [JobFilter!]!
|
||||
$sorting: OrderByInput!
|
||||
$paging: PageRequest!
|
||||
) {
|
||||
jobs(filter: $filter, order: $sorting, page: $paging) {
|
||||
items {
|
||||
user
|
||||
project
|
||||
exclusive
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
export let nodeJobsData = null;
|
||||
|
||||
// Not at least one returned, selected metric: NodeHealth warning
|
||||
const healthWarn = !dataHealth.includes(true);
|
||||
// At least one non-returned selected metric: Metric config error?
|
||||
const metricWarn = dataHealth.includes(false);
|
||||
|
||||
$: nodeJobsData = queryStore({
|
||||
client: client,
|
||||
query: nodeJobsQuery,
|
||||
variables: { paging, sorting, filter },
|
||||
});
|
||||
|
||||
let userList;
|
||||
let projectList;
|
||||
$: if ($nodeJobsData?.data) {
|
||||
userList = Array.from(new Set($nodeJobsData.data.jobs.items.map((j) => j.user))).sort((a, b) => a.localeCompare(b));
|
||||
projectList = Array.from(new Set($nodeJobsData.data.jobs.items.map((j) => j.project))).sort((a, b) => a.localeCompare(b));
|
||||
$: if (nodeJobsData) {
|
||||
userList = Array.from(new Set(nodeJobsData.jobs.items.map((j) => j.user))).sort((a, b) => a.localeCompare(b));
|
||||
projectList = Array.from(new Set(nodeJobsData.jobs.items.map((j) => j.project))).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<Card class="pb-3">
|
||||
@ -92,9 +54,6 @@
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
{#if $nodeJobsData.fetching}
|
||||
<Spinner />
|
||||
{:else if $nodeJobsData.data}
|
||||
{#if healthWarn}
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
@ -119,7 +78,7 @@
|
||||
Missing Metric
|
||||
</Button>
|
||||
</InputGroup>
|
||||
{:else if $nodeJobsData.data.jobs.count == 1 && $nodeJobsData.data.jobs.items[0].exclusive}
|
||||
{:else if nodeJobsData.jobs.count == 1 && nodeJobsData.jobs.items[0].exclusive}
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
<Icon name="circle-fill"/>
|
||||
@ -131,7 +90,7 @@
|
||||
Exclusive
|
||||
</Button>
|
||||
</InputGroup>
|
||||
{:else if $nodeJobsData.data.jobs.count >= 1 && !$nodeJobsData.data.jobs.items[0].exclusive}
|
||||
{:else if nodeJobsData.jobs.count >= 1 && !nodeJobsData.jobs.items[0].exclusive}
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
<Icon name="circle-half"/>
|
||||
@ -165,7 +124,7 @@
|
||||
<InputGroupText class="justify-content-center" style="width: 4.4rem;">
|
||||
Activity
|
||||
</InputGroupText>
|
||||
<Input class="flex-grow-1" style="background-color: white;" type="text" value="{$nodeJobsData?.data?.jobs?.count || 0} Job{($nodeJobsData?.data?.jobs?.count == 1) ? '': 's'}" disabled />
|
||||
<Input class="flex-grow-1" style="background-color: white;" type="text" value="{nodeJobsData?.jobs?.count || 0} Job{(nodeJobsData?.jobs?.count == 1) ? '': 's'}" 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" />
|
||||
List
|
||||
@ -213,7 +172,6 @@
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
{/if}
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
|
@ -8,7 +8,12 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { Card } from "@sveltestrap/sveltestrap";
|
||||
import {
|
||||
queryStore,
|
||||
gql,
|
||||
getContextClient,
|
||||
} from "@urql/svelte";
|
||||
import { Card, CardBody, Spinner } from "@sveltestrap/sveltestrap";
|
||||
import { maxScope, checkMetricDisabled } from "../../generic/utils.js";
|
||||
import MetricPlot from "../../generic/plots/MetricPlot.svelte";
|
||||
import NodeInfo from "./NodeInfo.svelte";
|
||||
@ -17,6 +22,43 @@
|
||||
export let nodeData;
|
||||
export let selectedMetrics;
|
||||
|
||||
const client = getContextClient();
|
||||
const paging = { itemsPerPage: 50, page: 1 };
|
||||
const sorting = { field: "startTime", type: "col", order: "DESC" };
|
||||
const filter = [
|
||||
{ cluster: { eq: cluster } },
|
||||
{ node: { contains: nodeData.host } },
|
||||
{ state: ["running"] },
|
||||
];
|
||||
|
||||
const nodeJobsQuery = gql`
|
||||
query (
|
||||
$filter: [JobFilter!]!
|
||||
$sorting: OrderByInput!
|
||||
$paging: PageRequest!
|
||||
) {
|
||||
jobs(filter: $filter, order: $sorting, page: $paging) {
|
||||
items {
|
||||
jobId
|
||||
user
|
||||
project
|
||||
exclusive
|
||||
resources {
|
||||
hostname
|
||||
accelerators
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
$: nodeJobsData = queryStore({
|
||||
client: client,
|
||||
query: nodeJobsQuery,
|
||||
variables: { paging, sorting, filter },
|
||||
});
|
||||
|
||||
// Helper
|
||||
const selectScope = (nodeMetrics) =>
|
||||
nodeMetrics.reduce(
|
||||
@ -51,14 +93,44 @@
|
||||
let dataHealth;
|
||||
$: if (nodeData?.metrics) {
|
||||
refinedData = sortAndSelectScope(nodeData?.metrics)
|
||||
// Check data for series, skip disabled
|
||||
dataHealth = refinedData.filter((rd) => rd.disabled === false).map((enabled) => (enabled.data.metric.series.length > 0))
|
||||
}
|
||||
|
||||
let extendedLegendData = null;
|
||||
$: if ($nodeJobsData?.data) {
|
||||
// Get Shared State of Node: Only Build extended Legend For Shared Nodes
|
||||
if ($nodeJobsData.data.jobs.count >= 1 && !$nodeJobsData.data.jobs.items[0].exclusive) {
|
||||
const accSet = Array.from(new Set($nodeJobsData.data.jobs.items
|
||||
.map((i) => i.resources
|
||||
.filter((r) => r.hostname === nodeData.host)
|
||||
.map((r) => r.accelerators)
|
||||
)
|
||||
)).flat(2)
|
||||
|
||||
extendedLegendData = {}
|
||||
for (const accId of accSet) {
|
||||
const matchJob = $nodeJobsData.data.jobs.items.find((i) => i.resources.find((r) => r.accelerators.includes(accId)))
|
||||
extendedLegendData[accId] = {
|
||||
user: matchJob?.user ? matchJob?.user : '-',
|
||||
job: matchJob?.jobId ? matchJob?.jobId : '-',
|
||||
}
|
||||
}
|
||||
// Theoretically extendable for hwthreadIDs
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<NodeInfo {cluster} subCluster={nodeData.subCluster} hostname={nodeData.host} {dataHealth}/>
|
||||
{#if $nodeJobsData.fetching}
|
||||
<Card>
|
||||
<CardBody class="content-center">
|
||||
<Spinner/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{:else}
|
||||
<NodeInfo nodeJobsData={$nodeJobsData.data} {cluster} subCluster={nodeData.subCluster} hostname={nodeData.host} {dataHealth}/>
|
||||
{/if}
|
||||
</td>
|
||||
{#each refinedData as metricData (metricData.data.name)}
|
||||
<td>
|
||||
@ -83,6 +155,7 @@
|
||||
forNode
|
||||
/>
|
||||
<div class="my-2"/>
|
||||
{#key extendedLegendData}
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
subCluster={nodeData.subCluster}
|
||||
@ -91,8 +164,10 @@
|
||||
timestep={metricData.data.metric.timestep}
|
||||
series={metricData.data.metric.series}
|
||||
height={175}
|
||||
{extendedLegendData}
|
||||
forNode
|
||||
/>
|
||||
{/key}
|
||||
{:else}
|
||||
<MetricPlot
|
||||
{cluster}
|
||||
|
@ -8,6 +8,7 @@
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
const isAdmin = {{ .User.HasRole .Roles.admin }};
|
||||
const isSupport = {{ .User.HasRole .Roles.support }};
|
||||
const isApi = {{ .User.HasRole .Roles.api }};
|
||||
const username = {{ .User.Username }};
|
||||
const filterPresets = {{ .FilterPresets }};
|
||||
|
Loading…
Reference in New Issue
Block a user