cc-backend/web/frontend/src/StatsTable.svelte
Christoph Kluge b2aed2f16b Add 'project' to user table, add 'manager' role, conditional web render
- Addresses issues #40 #45 #82
- Reworked Navigation Header for all roles
- 'Manager' role added, can be assigned a project-id in config by admins
- BREAKING! -> Added 'project' column in SQLite3 table 'user'
- Manager-Assigned project will be added to all graphql filters: Only show Jobs and Users of given project
- 'My Jobs' Tab for all Roles
- Switched from Bool "isAdmin" to integer authLevels
- Removed critical data frontend logging
- Reworked repo.query.SecurityCheck()
2023-01-27 18:36:58 +01:00

125 lines
4.4 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:${job.cluster}`]
|| 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)}> <!-- log to click ', 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
cluster={job.cluster}
configName='job_view_nodestats_selectedMetrics'
allMetrics={new Set(allMetrics)}
bind:metrics={selectedMetrics}
bind:isOpen={isMetricSelectionOpen} />