mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-09-20 19:37:26 +02:00
123 lines
4.2 KiB
Svelte
123 lines
4.2 KiB
Svelte
|
<script>
|
||
|
import { getContext } from 'svelte'
|
||
|
import { Button, Table, InputGroup, InputGroupText, Icon } from 'sveltestrap'
|
||
|
import MetricSelection from './MetricSelection.svelte'
|
||
|
import StatsTableEntry from './StatsTableEntry.svelte'
|
||
|
import { maxScope } from './utils.js'
|
||
|
|
||
|
export let job
|
||
|
export let jobMetrics
|
||
|
|
||
|
const allMetrics = [...new Set(jobMetrics.map(m => m.name))].sort(),
|
||
|
scopesForMetric = (metric) => jobMetrics
|
||
|
.filter(jm => jm.name == metric)
|
||
|
.map(jm => jm.metric.scope)
|
||
|
|
||
|
let hosts = job.resources.map(r => r.hostname).sort(),
|
||
|
selectedScopes = {},
|
||
|
sorting = {},
|
||
|
isMetricSelectionOpen = false,
|
||
|
selectedMetrics = getContext('cc-config').job_view_nodestats_selectedMetrics
|
||
|
|
||
|
for (let metric of allMetrics) {
|
||
|
selectedScopes[metric] = maxScope(scopesForMetric(metric))
|
||
|
sorting[metric] = {
|
||
|
min: { dir: 'up', active: false },
|
||
|
avg: { dir: 'up', active: false },
|
||
|
max: { dir: 'up', active: false },
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export function sortBy(metric, stat) {
|
||
|
let s = sorting[metric][stat]
|
||
|
if (s.active) {
|
||
|
s.dir = s.dir == 'up' ? 'down' : 'up'
|
||
|
} else {
|
||
|
for (let metric in sorting)
|
||
|
for (let stat in sorting[metric])
|
||
|
sorting[metric][stat].active = false
|
||
|
s.active = true
|
||
|
}
|
||
|
|
||
|
let series = jobMetrics.find(jm => jm.name == metric && jm.metric.scope == 'node')?.metric.series
|
||
|
sorting = {...sorting}
|
||
|
hosts = hosts.sort((h1, h2) => {
|
||
|
let s1 = series.find(s => s.hostname == h1)?.statistics
|
||
|
let s2 = series.find(s => s.hostname == h2)?.statistics
|
||
|
if (s1 == null || s2 == null)
|
||
|
return -1
|
||
|
|
||
|
return s.dir != 'up' ? s1[stat] - s2[stat] : s2[stat] - s1[stat]
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export function moreLoaded(jobMetric) {
|
||
|
jobMetrics = [...jobMetrics, jobMetric]
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<Table>
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>
|
||
|
<Button outline on:click={() => (isMetricSelectionOpen = true, console.log(isMetricSelectionOpen))}>
|
||
|
Metrics
|
||
|
</Button>
|
||
|
</th>
|
||
|
{#each selectedMetrics as metric}
|
||
|
<th colspan={selectedScopes[metric] == 'node' ? 3 : 4}>
|
||
|
<InputGroup>
|
||
|
<InputGroupText>
|
||
|
{metric}
|
||
|
</InputGroupText>
|
||
|
<select class="form-select"
|
||
|
bind:value={selectedScopes[metric]}
|
||
|
disabled={scopesForMetric(metric, jobMetrics).length == 1}>
|
||
|
{#each scopesForMetric(metric, jobMetrics) as scope}
|
||
|
<option value={scope}>{scope}</option>
|
||
|
{/each}
|
||
|
</select>
|
||
|
</InputGroup>
|
||
|
</th>
|
||
|
{/each}
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<th>Node</th>
|
||
|
{#each selectedMetrics as metric}
|
||
|
{#if selectedScopes[metric] != 'node'}
|
||
|
<th>Id</th>
|
||
|
{/if}
|
||
|
{#each ['min', 'avg', 'max'] as stat}
|
||
|
<th on:click={() => sortBy(metric, stat)}>
|
||
|
{stat}
|
||
|
{#if selectedScopes[metric] == 'node'}
|
||
|
<Icon name="caret-{sorting[metric][stat].dir}{sorting[metric][stat].active ? '-fill' : ''}" />
|
||
|
{/if}
|
||
|
</th>
|
||
|
{/each}
|
||
|
{/each}
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
{#each hosts as host (host)}
|
||
|
<tr>
|
||
|
<th scope="col">{host}</th>
|
||
|
{#each selectedMetrics as metric (metric)}
|
||
|
<StatsTableEntry
|
||
|
host={host} metric={metric}
|
||
|
scope={selectedScopes[metric]}
|
||
|
jobMetrics={jobMetrics} />
|
||
|
{/each}
|
||
|
</tr>
|
||
|
{/each}
|
||
|
</tbody>
|
||
|
</Table>
|
||
|
|
||
|
<br/>
|
||
|
|
||
|
<MetricSelection
|
||
|
configName='job_view_nodestats_selectedMetrics'
|
||
|
allMetrics={allMetrics}
|
||
|
bind:metrics={selectedMetrics}
|
||
|
bind:isOpen={isMetricSelectionOpen} />
|