mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-26 06:06:15 +02:00
Move common logic into systems view again
- adds backend log if subcluster for node not configured
This commit is contained in:
@@ -9,36 +9,259 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import { queryStore, gql, getContextClient } from "@urql/svelte"
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupText,
|
||||
Icon,
|
||||
Button,
|
||||
Spinner,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
import { init, checkMetricsDisabled } from "./generic/utils.js";
|
||||
import NodeOverview from "./systems/NodeOverview.svelte";
|
||||
import NodeList from "./systems/NodeList.svelte";
|
||||
import MetricSelection from "./generic/select/MetricSelection.svelte";
|
||||
import TimeSelection from "./generic/select/TimeSelection.svelte";
|
||||
import Refresher from "./generic/helper/Refresher.svelte";
|
||||
|
||||
export let displayType;
|
||||
export let cluster;
|
||||
export let from = null;
|
||||
export let to = null;
|
||||
|
||||
const { query: initq } = init();
|
||||
|
||||
console.assert(
|
||||
displayType == "OVERVIEW" || displayType == "LIST",
|
||||
"Invalid nodes displayType provided!",
|
||||
);
|
||||
|
||||
if (from == null || to == null) {
|
||||
to = new Date(Date.now());
|
||||
from = new Date(to.getTime());
|
||||
from.setHours(from.getHours() - 12);
|
||||
}
|
||||
|
||||
const initialized = getContext("initialized");
|
||||
const ccconfig = getContext("cc-config");
|
||||
const globalMetrics = getContext("globalMetrics");
|
||||
const displayNodeOverview = (displayType === 'OVERVIEW')
|
||||
|
||||
let nodeList;
|
||||
let hostnameFilter = "";
|
||||
let selectedMetric = ccconfig.system_view_selectedMetric || "";
|
||||
let selectedMetrics = ccconfig.node_list_selectedMetrics || ['cpu_load', 'mem_bw', 'acc_utilization', 'net_bytes_in', 'net_bytes_out'];
|
||||
let isMetricsSelectionOpen = false;
|
||||
|
||||
const client = getContextClient();
|
||||
const nodeQuery = gql`
|
||||
query ($cluster: String!, $metrics: [String!], $from: Time!, $to: Time!) {
|
||||
nodeMetrics(
|
||||
cluster: $cluster
|
||||
metrics: $metrics
|
||||
from: $from
|
||||
to: $to
|
||||
) {
|
||||
host
|
||||
subCluster
|
||||
metrics {
|
||||
name
|
||||
scope
|
||||
metric {
|
||||
timestep
|
||||
unit {
|
||||
base
|
||||
prefix
|
||||
}
|
||||
series {
|
||||
statistics {
|
||||
min
|
||||
avg
|
||||
max
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
$: nodesQuery = queryStore({
|
||||
client: client,
|
||||
query: nodeQuery,
|
||||
variables: {
|
||||
cluster: cluster,
|
||||
metrics: selectedMetrics,
|
||||
from: from.toISOString(),
|
||||
to: to.toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
let systemMetrics = [];
|
||||
let systemUnits = {};
|
||||
function loadMetrics(isInitialized) {
|
||||
if (!isInitialized) return
|
||||
systemMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))]
|
||||
for (let sm of systemMetrics) {
|
||||
systemUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "")
|
||||
}
|
||||
}
|
||||
|
||||
$: loadMetrics($initialized)
|
||||
|
||||
$: if (displayNodeOverview) {
|
||||
selectedMetrics = [selectedMetric]
|
||||
}
|
||||
|
||||
let rawData = []
|
||||
$: if ($initq.data && $nodesQuery?.data) {
|
||||
rawData = $nodesQuery?.data?.nodeMetrics.filter((h) => {
|
||||
if (h.subCluster === '') { // Exclude nodes with empty subCluster field
|
||||
console.warn('subCluster not configured for node', h.host)
|
||||
return false
|
||||
} else {
|
||||
return h.metrics.some(
|
||||
(m) => selectedMetrics.includes(m.name) && m.scope == "node",
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mappedData = []
|
||||
$: if (rawData?.length > 0) {
|
||||
mappedData = rawData.map((h) => ({
|
||||
host: h.host,
|
||||
subCluster: h.subCluster,
|
||||
data: h.metrics.find(
|
||||
(m) => selectedMetrics.includes(m.name) && m.scope == "node",
|
||||
),
|
||||
disabled: checkMetricsDisabled(
|
||||
selectedMetrics,
|
||||
cluster,
|
||||
h.subCluster,
|
||||
),
|
||||
}))
|
||||
.sort((a, b) => a.host.localeCompare(b.host))
|
||||
}
|
||||
|
||||
let filteredData = []
|
||||
$: if (mappedData?.length > 0) {
|
||||
filteredData = mappedData.filter((h) =>
|
||||
h.host.includes(hostnameFilter)
|
||||
)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if displayType === 'OVERVIEW'}
|
||||
<NodeOverview {cluster} {from} {to}/>
|
||||
{:else if displayType === 'LIST'}
|
||||
<NodeList {cluster} {from} {to}/>
|
||||
{:else}
|
||||
<Row>
|
||||
<Col>
|
||||
<Card color="danger">
|
||||
Unknown displayList type!
|
||||
</Card>
|
||||
</Col>
|
||||
<!-- ROW1: Tools-->
|
||||
<Row cols={{ xs: 2, lg: 4 }}>
|
||||
{#if $initq.data}
|
||||
<!-- List Metric Select Col-->
|
||||
{#if !displayNodeOverview}
|
||||
<Col>
|
||||
<InputGroup>
|
||||
<InputGroupText><Icon name="graph-up" /></InputGroupText>
|
||||
<InputGroupText>Metrics</InputGroupText>
|
||||
<Button
|
||||
outline
|
||||
color="secondary"
|
||||
on:click={() => (isMetricsSelectionOpen = true)}
|
||||
>
|
||||
Select for {cluster} ...
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
{/if}
|
||||
<!-- Node Col-->
|
||||
<Col>
|
||||
<InputGroup>
|
||||
<InputGroupText><Icon name="hdd" /></InputGroupText>
|
||||
<InputGroupText>Find Node(s)</InputGroupText>
|
||||
<Input
|
||||
placeholder="Filter hostname ..."
|
||||
type="text"
|
||||
title="Search with at least three characters ..."
|
||||
bind:value={hostnameFilter}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<!-- Range Col-->
|
||||
<Col>
|
||||
<TimeSelection bind:from bind:to />
|
||||
</Col>
|
||||
<!-- Overview Metric Col-->
|
||||
{#if displayNodeOverview}
|
||||
<Col class="mt-2 mt-lg-0">
|
||||
<InputGroup>
|
||||
<InputGroupText><Icon name="graph-up" /></InputGroupText>
|
||||
<InputGroupText>Metric</InputGroupText>
|
||||
<Input type="select" bind:value={selectedMetric}>
|
||||
{#each systemMetrics as metric}
|
||||
<option value={metric.name}
|
||||
>{metric.name} {systemUnits[metric.name] ? "("+systemUnits[metric.name]+")" : ""}</option
|
||||
>
|
||||
{/each}
|
||||
</Input>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
{/if}
|
||||
<!-- Refresh Col-->
|
||||
<Col class="mt-2 mt-lg-0">
|
||||
<Refresher
|
||||
on:refresh={() => {
|
||||
const diff = Date.now() - to;
|
||||
from = new Date(from.getTime() + diff);
|
||||
to = new Date(to.getTime() + diff);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
{/if}
|
||||
</Row>
|
||||
|
||||
<!-- ROW2: Content-->
|
||||
{#if displayType !== "OVERVIEW" && displayType !== "LIST"}
|
||||
<Row>
|
||||
<Col>
|
||||
<Card body color="danger">Unknown displayList type! </Card>
|
||||
</Col>
|
||||
</Row>
|
||||
{:else if $nodesQuery.error}
|
||||
<Row>
|
||||
<Col>
|
||||
<Card body color="danger">{$nodesQuery.error.message}</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
{:else if $nodesQuery.fetching }
|
||||
<Row>
|
||||
<Col>
|
||||
<Spinner />
|
||||
</Col>
|
||||
</Row>
|
||||
{:else if $initialized && $nodesQuery?.data}
|
||||
{#if displayNodeOverview}
|
||||
<!-- ROW2-1: Node Overview (Grid Included)-->
|
||||
<NodeOverview {ccconfig} {cluster} bind:selectedMetric data={filteredData}/>
|
||||
|
||||
{:else}
|
||||
<!-- ROW2-2: Node List-->
|
||||
<Row>
|
||||
<Col>
|
||||
<!-- <NodeList bind:nodesData={$nodesQuery.data} {cluster} {selectedMetrics} {systemUnits} bind:hostnameFilter/> -->
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<MetricSelection
|
||||
{cluster}
|
||||
configName="node_list_selectedMetrics"
|
||||
bind:metrics={selectedMetrics}
|
||||
bind:isOpen={isMetricsSelectionOpen}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
Reference in New Issue
Block a user