Skip to content

Commit 289dc9e

Browse files
committed
add severity policy list view
Signed-off-by: Frank Jogeleit <[email protected]>
1 parent 19fa304 commit 289dc9e

File tree

18 files changed

+194
-32
lines changed

18 files changed

+194
-32
lines changed

backend/pkg/api/core/model.go

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
package core
22

3+
type Status struct {
4+
Pass int `json:"pass"`
5+
Skip int `json:"skip"`
6+
Warn int `json:"warn"`
7+
Error int `json:"error"`
8+
Fail int `json:"fail"`
9+
}
10+
11+
type Severities struct {
12+
Low int `json:"low"`
13+
Info int `json:"info"`
14+
Medium int `json:"medium"`
15+
High int `json:"high"`
16+
Critical int `json:"critical"`
17+
}
18+
319
type Category struct {
4-
Name string `json:"name"`
5-
Pass int `json:"pass"`
6-
Skip int `json:"skip"`
7-
Warn int `json:"warn"`
8-
Error int `json:"error"`
9-
Fail int `json:"fail"`
20+
Name string `json:"name"`
21+
Status Status `json:"status"`
22+
Severities Severities `json:"severities"`
1023
}
1124

1225
type Policy struct {

backend/pkg/api/model/model.go

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ const (
1414
Skip string = "skip"
1515
)
1616

17+
const (
18+
Severity string = "severity"
19+
Status string = "status"
20+
)
21+
1722
type Endpoints struct {
1823
Core *core.Client
1924
Plugins map[string]*plugin.Client
@@ -22,6 +27,7 @@ type Endpoints struct {
2227
type SourceConfig struct {
2328
Results []string
2429
Exceptions bool
30+
ChartType string
2531
}
2632

2733
func (s SourceConfig) EnabledResults() []string {

backend/pkg/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ type Server struct {
186186
type Source struct {
187187
Name string `mapstructure:"name"`
188188
Exceptions bool `mapstructure:"exceptions"`
189+
ChartType string `mapstructure:"chartType"`
189190
Excludes struct {
190191
NamespaceKinds []string `mapstructure:"namespaceKinds"`
191192
ClusterKinds []string `mapstructure:"clusterKinds"`

backend/pkg/config/mapper.go

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func MapConfig(c *Config) *api.Config {
3838
Sources: utils.Map(c.Sources, func(s Source) api.Source {
3939
return api.Source{
4040
Name: s.Name,
41+
ChartType: s.ChartType,
4142
Exceptions: s.Exceptions,
4243
Excludes: api.Excludes{
4344
NamespaceKinds: s.Excludes.NamespaceKinds,

backend/pkg/server/api/handler.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ func (h *Handler) NamespaceReport(ctx *gin.Context) {
351351
func NewHandler(config *Config, apis map[string]*model.Endpoints, customBoards map[string]CustomBoard) *Handler {
352352
sources := make(map[string]model.SourceConfig, len(config.Sources))
353353
for _, s := range config.Sources {
354-
sources[s.Name] = model.SourceConfig{Results: s.Excludes.Results, Exceptions: s.Exceptions}
354+
sources[s.Name] = model.SourceConfig{Results: s.Excludes.Results, Exceptions: s.Exceptions, ChartType: s.ChartType}
355355
}
356356

357357
return &Handler{config, apis, customBoards, service.New(apis, sources), reports.New(apis)}

backend/pkg/server/api/model.go

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Excludes struct {
2424

2525
type Source struct {
2626
Name string `json:"name"`
27+
ChartType string `mapstructure:"chartType"`
2728
Exceptions bool `mapstructure:"exceptions"`
2829
Excludes Excludes `json:"excludes"`
2930
}

backend/pkg/service/mapper.go

+68-6
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import (
77
plugin "github.com/kyverno/policy-reporter-plugins/sdk/api"
88
pluginAPI "github.com/kyverno/policy-reporter-plugins/sdk/api"
99
"github.com/kyverno/policy-reporter-ui/pkg/api/core"
10+
"github.com/kyverno/policy-reporter-ui/pkg/api/model"
1011
"github.com/kyverno/policy-reporter-ui/pkg/utils"
1112
"golang.org/x/exp/maps"
1213
)
1314

1415
var allStatus = []string{StatusPass, StatusWarn, StatusFail, StatusError, StatusSkip}
16+
var allSeverities = []string{SeverityUnknown, SeverityInfo, SeverityLow, SeverityMedium, SeverityHigh, SeverityCritical}
1517

1618
func MapFindingSourcesToSourceItem(findings *core.Findings) []SourceItem {
1719
findingSources := make(map[string]bool, 0)
@@ -308,7 +310,66 @@ func MapResourceSourceChart(results []core.ResourceStatusCount, status []string)
308310
}
309311
}
310312

311-
func MapCategoriesToChart(title string, categories []core.Category, status []string) *Chart {
313+
func MapCategorySeveritiesToChart(title string, categories []core.Category, severities []string) *Chart {
314+
var sets = make(map[string]*Dataset)
315+
if len(severities) == 0 {
316+
severities = allSeverities
317+
}
318+
319+
for _, s := range severities {
320+
sets[s] = &Dataset{Label: utils.Title(s), Data: make([]int, 0)}
321+
}
322+
323+
labels := make([]string, 0, len(categories))
324+
sorting := make(map[string]int, len(categories))
325+
326+
for index, category := range categories {
327+
sorting[category.Name] = index
328+
labels = append(labels, category.Name)
329+
330+
mapping := map[string]int{
331+
SeverityLow: category.Severities.Low,
332+
SeverityInfo: category.Severities.Info,
333+
SeverityMedium: category.Severities.Medium,
334+
SeverityHigh: category.Severities.High,
335+
SeverityCritical: category.Severities.Critical,
336+
}
337+
338+
for _, s := range severities {
339+
sets[s].Data = append(sets[s].Data, mapping[s])
340+
}
341+
}
342+
343+
sort.Slice(labels, func(i, j int) bool {
344+
return labels[i] < labels[j]
345+
})
346+
347+
// sorting Data to the same order as related labels
348+
for _, set := range sets {
349+
data := make([]int, 0, len(set.Data))
350+
for _, label := range labels {
351+
data = append(data, set.Data[sorting[label]])
352+
}
353+
354+
set.Data = data
355+
}
356+
357+
datasets := make([]*Dataset, 0, len(sets))
358+
for _, s := range allSeverities {
359+
if set, ok := sets[s]; ok {
360+
datasets = append(datasets, set)
361+
}
362+
}
363+
364+
return &Chart{
365+
Name: title,
366+
Labels: labels,
367+
Datasets: datasets,
368+
Type: model.Severity,
369+
}
370+
}
371+
372+
func MapCategoryStatusToChart(title string, categories []core.Category, status []string) *Chart {
312373
var sets = make(map[string]*Dataset)
313374
if len(status) == 0 {
314375
status = allStatus
@@ -326,11 +387,11 @@ func MapCategoriesToChart(title string, categories []core.Category, status []str
326387
labels = append(labels, category.Name)
327388

328389
mapping := map[string]int{
329-
StatusPass: category.Pass,
330-
StatusWarn: category.Warn,
331-
StatusFail: category.Fail,
332-
StatusError: category.Error,
333-
StatusSkip: category.Skip,
390+
StatusPass: category.Status.Pass,
391+
StatusWarn: category.Status.Warn,
392+
StatusFail: category.Status.Fail,
393+
StatusError: category.Status.Error,
394+
StatusSkip: category.Status.Skip,
334395
}
335396

336397
for _, s := range status {
@@ -363,6 +424,7 @@ func MapCategoriesToChart(title string, categories []core.Category, status []str
363424
Name: title,
364425
Labels: labels,
365426
Datasets: datasets,
427+
Type: model.Status,
366428
}
367429
}
368430

backend/pkg/service/model.go

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ const (
1010
StatusSkip = "skip"
1111
)
1212

13+
const (
14+
SeverityUnknown = "unknown"
15+
SeverityLow = "low"
16+
SeverityInfo = "info"
17+
SeverityMedium = "medium"
18+
SeverityHigh = "high"
19+
SeverityCritical = "critical"
20+
)
21+
1322
type SourceItem struct {
1423
Title string `json:"title"`
1524
Name string `json:"name"`
@@ -25,6 +34,7 @@ type Chart struct {
2534
Labels []string `json:"labels"`
2635
Datasets []*Dataset `json:"datasets"`
2736
Name string `json:"name"`
37+
Type string `json:"type"`
2838
}
2939

3040
type ChartVariants struct {

backend/pkg/service/service.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,20 @@ func (s *Service) PolicySources(ctx context.Context, cluster string, query url.V
151151

152152
status := s.configs[source.Name].EnabledResults()
153153

154+
var chart *Chart
155+
if s.configs[source.Name].ChartType == model.Severity {
156+
chart = MapCategorySeveritiesToChart(title, source.Categories, []string{})
157+
status = []string{"summary"}
158+
} else {
159+
chart = MapCategoryStatusToChart(title, source.Categories, status)
160+
}
161+
154162
list = append(list, Source{
155163
Name: source.Name,
156164
Title: title,
157165
Status: status,
158166
Categories: categories,
159-
Chart: MapCategoriesToChart(title, source.Categories, status),
167+
Chart: chart,
160168
})
161169
}
162170

@@ -308,7 +316,7 @@ func (s *Service) ResourceDetails(ctx context.Context, cluster, id string, query
308316
Categories: categories,
309317
Status: status,
310318
Exceptions: config.Exceptions,
311-
Chart: MapCategoriesToChart(title, source.Categories, config.EnabledResults()),
319+
Chart: MapCategoryStatusToChart(title, source.Categories, config.EnabledResults()),
312320
})
313321
}
314322

frontend/composables/status.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const useStatusProvider = (data?: Ref<{ status: Status[] } | null>) => {
66
provide(ShowedStatus, computed(() => {
77
const status = data?.value?.status
88
if (status && status.length) {
9-
return [Status.SKIP, Status.PASS, Status.WARN, Status.FAIL, Status.ERROR].reduce<Status[]>((acc, s) => {
9+
return [Status.SKIP, Status.PASS, Status.WARN, Status.FAIL, Status.ERROR, Status.SUMMARY].reduce<Status[]>((acc, s) => {
1010
if (status.includes(s)) { return [...acc, s] }
1111

1212
return acc

frontend/modules/core/components/ResultChip.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<script setup lang="ts">
99
import { Status } from '../types'
1010
11-
const props = defineProps<{
11+
defineProps<{
1212
status: Status;
1313
count?: number;
1414
tooltip: string;
@@ -20,5 +20,6 @@ const icons = {
2020
[Status.FAIL]: 'alert-circle',
2121
[Status.ERROR]: 'close',
2222
[Status.SKIP]: 'slash-forward',
23+
[Status.SUMMARY]: 'alert',
2324
}
2425
</script>

frontend/modules/core/components/graph/StatusPerCategory.vue frontend/modules/core/components/graph/BarPerCategory.vue

+19-3
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,37 @@
55
<script setup lang="ts">
66
import { Bar } from 'vue-chartjs'
77
import { type Chart } from '../../types'
8-
import { useStatusColors } from "~/modules/core/composables/theme";
8+
import {useSeverityColors, useStatusColors} from "~/modules/core/composables/theme";
99
1010
const props = defineProps<{ source: Chart }>()
1111
1212
const colors = useChartColors()
13+
14+
const severityColors = useSeverityColors()
1315
const statusColors = useStatusColors()
1416
17+
const config = computed(() => {
18+
if (props.source.type === 'severity') {
19+
return {
20+
colors: severityColors.value,
21+
title: `Severities per Category`
22+
}
23+
}
24+
25+
return {
26+
colors: statusColors.value,
27+
title: `Results per Category`
28+
}
29+
})
30+
1531
const chart = computed(() => {
1632
return {
1733
style: {
1834
minHeight: `${125 + (props.source.labels.length * 25)}px`
1935
},
2036
data: {
2137
labels: props.source.labels,
22-
datasets: props.source.datasets.map((d) => ({ ...d, backgroundColor: statusColors.value[d.label?.toLowerCase()] }))
38+
datasets: props.source.datasets.map((d) => ({ ...d, backgroundColor: config.value.colors[d.label?.toLowerCase()] }))
2339
},
2440
options: {
2541
color: colors.value.color,
@@ -32,7 +48,7 @@ const chart = computed(() => {
3248
plugins: {
3349
title: {
3450
display: true,
35-
text: `Results per Category`
51+
text: config.value.title
3652
},
3753
legend: {
3854
display: true,

frontend/modules/core/components/policy/Item.vue

+10-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
<v-list-item-title>
99
{{ item.title }}
1010
</v-list-item-title>
11-
<template v-slot:append>
11+
<template v-slot:append v-if="summary">
12+
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { kinds: route.query?.kinds, 'cluster-kinds': route.query['cluster-kinds'] }}" class="ml-2" :status="Status.SUMMARY" :count="count" tooltip="results" />
13+
</template>
14+
15+
<template v-slot:append v-else>
1216
<ResultChip v-if="showSkipped" :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.SKIP, kinds: route.query?.kinds, 'cluster-kinds': route.query['cluster-kinds'] }}" class="ml-2" :status="Status.SKIP" :count="item.results[Status.SKIP]" tooltip="skip results" />
13-
<template v-for="status in showed" :key="status">
17+
<template v-for="status in showStatus" :key="status">
1418
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status, kinds: route.query?.kinds, 'cluster-kinds': route.query['cluster-kinds'] }}" class="ml-2" :status="status" :count="item.results[status]" :tooltip="`${status} results`" />
1519
</template>
1620
</template>
@@ -37,10 +41,13 @@ const props = defineProps({
3741
item: { type: Object as PropType<PolicyResult>, required: true },
3842
details: { type: Boolean, default: false },
3943
filter: { type: Object as PropType<Filter>, required: false },
44+
showStatus: { type: Array as PropType<Status[]>, required: true },
45+
summary: { type: Boolean, default: false },
4046
})
4147
4248
const status = useStatusInjection()
4349
4450
const showSkipped = computed(() => status.value.includes(Status.SKIP) && !!props.item?.results[Status.SKIP])
45-
const showed = computed(() => status.value.filter((s) => s !== Status.SKIP))
51+
52+
const count = computed(() => Object.values(props.item?.results || {}).reduce((sum, v) => sum + v, 0))
4653
</script>

frontend/modules/core/components/policy/List.vue

+7-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<v-list v-if="list.length && open" lines="two" class="mt-0 pt-0 pb-0 mb-0">
1616
<policy-list-scroller :list="list" :default-loadings="20" key-prop="name">
1717
<template #default="{ item }">
18-
<PolicyItem :item="item" :details="false" />
18+
<PolicyItem :item="item" :details="false" :show-status="showed" :summary="summary" />
1919
</template>
2020
</policy-list-scroller>
2121
</v-list>
@@ -30,7 +30,7 @@
3030

3131
<script setup lang="ts">
3232
import CollapseBtn from "~/components/CollapseBtn.vue";
33-
import type { PolicyResult } from "~/modules/core/types";
33+
import {type PolicyResult, Status} from "~/modules/core/types";
3434
3535
const props = defineProps<{ category: string; policies: PolicyResult[]; pending: boolean; }>()
3636
@@ -43,4 +43,9 @@ const list = computed(() => {
4343
return props.policies.filter((p) => p.title.toLowerCase().includes(search.value.toLowerCase()))
4444
})
4545
46+
47+
const status = useStatusInjection()
48+
49+
const showed = computed(() => status.value.filter((s) => s !== Status.SKIP))
50+
const summary = computed(() => status.value.includes(Status.SUMMARY))
4651
</script>

frontend/modules/core/components/policy/SourceGroup.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<div v-show="open">
1111
<v-divider/>
1212
<v-card-text>
13-
<GraphStatusPerCategory :source="source.chart"/>
13+
<GraphBarPerCategory :source="source.chart" />
1414
</v-card-text>
1515
<policy-list v-for="item in source.categories"
1616
:key="item"

0 commit comments

Comments
 (0)