Skip to content

Commit

Permalink
improve policy dashboards
Browse files Browse the repository at this point in the history
Signed-off-by: Frank Jogeleit <[email protected]>
  • Loading branch information
Frank Jogeleit committed Mar 14, 2024
1 parent fa65d1f commit 0c5897b
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 61 deletions.
69 changes: 42 additions & 27 deletions backend/pkg/service/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,47 @@ func MapFindingsToSourceStatusChart(title string, findings *core.Findings) *Char
}
}

func MapNamespaceScopeChartVariant(title string, namespaces core.NamespaceStatusCounts) *ChartVariants {
chart := &ChartVariants{
Complete: MapNamespaceStatusCountsToChart(title, namespaces),
}

if len(namespaces) > 8 {
prev := make(core.NamespaceStatusCounts, 8)
ns := maps.Keys(namespaces)
sort.Strings(ns)

for _, v := range ns {
if hasFailure(namespaces[v]) {
prev[v] = namespaces[v]
}

if len(prev) >= 7 {
break
}
}

if len(prev) == 0 {
for _, v := range ns[0:6] {
prev[v] = namespaces[v]
}
} else if len(prev) < 7 {
for _, v := range ns {
if _, ok := prev[v]; !ok {
prev[v] = namespaces[v]
}
if len(prev) >= 7 {
break
}
}
}

chart.Preview = MapNamespaceStatusCountsToChart(title, prev)
}

return chart
}

func MapNamespaceStatusCountsToChart(title string, namespaces core.NamespaceStatusCounts) *Chart {
sets := map[string]*Dataset{
StatusPass: {Label: utils.Title(StatusPass), Data: make([]int, 0)},
Expand Down Expand Up @@ -163,33 +204,7 @@ func MapNamespaceStatusCountsToCharts(findings map[string]core.NamespaceStatusCo
charts := make(map[string]*ChartVariants, len(findings))

for source, namespaces := range findings {
charts[source] = &ChartVariants{
Complete: MapNamespaceStatusCountsToChart(utils.Title(source), namespaces),
}

if len(namespaces) > 8 {
prev := make(core.NamespaceStatusCounts, 8)
ns := maps.Keys(namespaces)
sort.Strings(ns)

for _, v := range ns {
if hasFailure(namespaces[v]) {
prev[v] = namespaces[v]
}

if len(prev) >= 7 {
break
}
}

if len(prev) == 0 {
for _, v := range ns[0:6] {
prev[v] = namespaces[v]
}
}

charts[source].Preview = MapNamespaceStatusCountsToChart(utils.Title(source), prev)
}
charts[source] = MapNamespaceScopeChartVariant(utils.Title(source), namespaces)
}

return charts
Expand Down
2 changes: 1 addition & 1 deletion backend/pkg/service/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ type Source struct {

type PolicyCharts struct {
Findings *Chart `json:"findings"`
NamespaceScope *Chart `json:"namespaceScope"`
NamespaceScope *ChartVariants `json:"namespaceScope"`
ClusterScope map[string]int `json:"clusterScope"`
}

Expand Down
2 changes: 1 addition & 1 deletion backend/pkg/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (s *Service) PolicyDetails(ctx context.Context, cluster, source, policy str
Name: policy,
Chart: PolicyCharts{
Findings: MapFindingsToSourceStatusChart(title, findings),
NamespaceScope: MapNamespaceStatusCountsToChart(title, result),
NamespaceScope: MapNamespaceScopeChartVariant(title, result),
ClusterScope: clusterResult,
},
}
Expand Down
6 changes: 4 additions & 2 deletions frontend/components/PageLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
<v-toolbar color="header" elevation="2">
<v-toolbar-title v-if="title">{{ title }}</v-toolbar-title>
<template #append>
<policy-report-dialog :source="source" :category="category" v-if="source" />
<slot name="prepend" />
<policy-report-dialog :source="source" :category="category" v-if="source && !hideReport" />
<FormKindAutocomplete style="min-width: 300px; max-width: 100%; margin-right: 15px;" v-model="kinds" :source="store || source" />
<FormClusterKindAutocomplete v-if="!nsScoped" style="min-width: 300px;" v-model="clusterKinds" :source="store || source" />
<slot name="append" />
</template>
</v-toolbar>
</v-card>
Expand All @@ -19,7 +21,7 @@
<script setup lang="ts">
import { ClusterKinds, NamespacedKinds } from "~/modules/core/provider/dashboard";
const props = defineProps<{ title?: string; category?: string; source?: string; store?: string; nsScoped?: boolean; kinds?: string[]; clusterKinds?: string[] }>()
const props = defineProps<{ title?: string; category?: string; source?: string; store?: string; nsScoped?: boolean; kinds?: string[]; clusterKinds?: string[]; hideReport: boolean }>()
const kinds = ref<string[]>(props.kinds ?? [])
const clusterKinds = ref<string[]>(props.clusterKinds ?? [])
Expand Down
10 changes: 7 additions & 3 deletions frontend/modules/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export class CoreAPI {
return exec<SourceDetails[]>(`/api/config/${this.cluster}/policy-sources`, { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
}

policyDetails (source: string, policy: string, namespace?: string, status?: Status[]) {
return exec<PolicyDetails>(`/api/config/${this.cluster}/${source}/policy/details`, { baseURL: this.baseURL, params: applyExcludes({ policies: [policy], namespace, status }, [...this.nsExcludes, ...this.clusterExcludes]) })
policyDetails (source: string, policy: string, namespace?: string, status?: Status[], kinds?: string[] ) {
return exec<PolicyDetails>(`/api/config/${this.cluster}/${source}/policy/details`, { baseURL: this.baseURL, params: applyExcludes({ policies: [policy], namespace, status, kinds }, [...this.nsExcludes, ...this.clusterExcludes], source) })
}

policyHTMLReport (source: string, filter: { namespaces: string[]; categories: string[]; kinds: string[]; clusterScope: boolean; }) {
Expand Down Expand Up @@ -166,7 +166,7 @@ const exec = <T>(api: string, opts: NitroFetchOptions<NitroFetchRequest>): Promi
return $fetch<T>(api, opts)
}

const applyExcludes = <T extends Filter>(filter: T | undefined, exclude: string[] | undefined) => {
const applyExcludes = <T extends Filter>(filter: T | undefined, exclude: string[] | undefined, source?: string) => {
if (!filter) return ({ exclude })

if (filter.kinds && filter.kinds.length > 0) return filter
Expand All @@ -177,6 +177,10 @@ const applyExcludes = <T extends Filter>(filter: T | undefined, exclude: string[
exclude = exclude.filter((e) => filter.sources?.some(s => e.startsWith(s)))
}

if (source) {
exclude = exclude.filter((e) => e.startsWith(source))
}

return {
...filter,
exclude
Expand Down
3 changes: 0 additions & 3 deletions frontend/modules/core/components/graph/SourceStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ import type { Dashboard } from "~/modules/core/types";
const props = defineProps<{ data: Dashboard; source: string; category?: string; }>();
console.log(props.data)
const expand = ref(false)
const hasPreview = computed(() => !!props.data.charts.namespaceScope[props.source].preview)
const showExpanded = computed(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { useChartColors, useStatusColors } from "~/modules/core/composables/them
import type { Chart } from "~/modules/core/types";
const props = defineProps<{ title?: string; data: Chart }>()
const colors = useChartColors()
const statusColors = useStatusColors()
Expand Down
10 changes: 6 additions & 4 deletions frontend/modules/core/components/policy/Item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
{{ item.title }}
</v-list-item-title>
<template v-slot:append>
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.PASS }}" :status="Status.PASS" :count="item.results.pass" tooltip="pass results" />
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.WARN }}" class="ml-2" :status="Status.WARN" :count="item.results.warn" tooltip="warning results" />
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.FAIL }}" class="ml-2" :status="Status.FAIL" :count="item.results.fail" tooltip="fail results" />
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.ERROR }}" class="ml-2" :status="Status.ERROR" :count="item.results.error" tooltip="error results" />
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.PASS, kinds: route.query?.kinds, 'cluster-kinds': route.query['cluster-kinds'] }}" :status="Status.PASS" :count="item.results.pass" tooltip="pass results" />
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.WARN, kinds: route.query?.kinds, 'cluster-kinds': route.query['cluster-kinds'] }}" class="ml-2" :status="Status.WARN" :count="item.results.warn" tooltip="warning results" />
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.FAIL, kinds: route.query?.kinds, 'cluster-kinds': route.query['cluster-kinds'] }}" class="ml-2" :status="Status.FAIL" :count="item.results.fail" tooltip="fail results" />
<ResultChip :to="{ name: 'policies-source-policy', params: { source: item.source, policy: item.name }, query: { status: Status.ERROR, kinds: route.query?.kinds, 'cluster-kinds': route.query['cluster-kinds'] }}" class="ml-2" :status="Status.ERROR" :count="item.results.error" tooltip="error results" />
</template>
</v-list-item>
<template v-if="open">
Expand All @@ -30,6 +30,8 @@ import { capilize } from "~/modules/core/layouthHelper";
const open = ref(false)
const route = useRoute()
const bg = useBGColor()
const props = defineProps({
Expand Down
21 changes: 19 additions & 2 deletions frontend/modules/core/components/policy/StatusCharts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@
</v-col>
<v-col cols="12" md="8">
<v-card style="height: 100%">
<v-card-text style="height: 100%">
<GraphStatusPerNamespace :data="data.charts.namespaceScope" />
<v-card-text style="height: 100%; position: relative;">
<GraphStatusPerNamespace v-if="showExpanded" :data="data.charts.namespaceScope.complete" />
<GraphStatusPerNamespace v-else :data="data.charts.namespaceScope.preview" />
<v-btn v-if="hasPreview" variant="outlined" size="small" @click="expand = !expand" style="position: absolute; bottom: 10px; right: 10px;" rounded="0">
<span v-if="showExpanded">Show preview</span>
<span v-else>Show Complete List</span>
</v-btn>
</v-card-text>
</v-card>
</v-col>
Expand All @@ -29,6 +34,18 @@ import type { PolicyDetails } from "~/modules/core/types";
const props = defineProps<{ data: PolicyDetails, hideCluster?: boolean; policy: string; }>();
const expand = ref(false)
const hasPreview = computed(() => !!props.data.charts.namespaceScope.preview)
const showExpanded = computed(() => {
if (!props.data.charts.namespaceScope.preview) {
return true
}
return expand.value
})
const clusterScope = computed(() => {
if (props.hideCluster) return false
Expand Down
5 changes: 4 additions & 1 deletion frontend/modules/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@ export type PolicyDetails = {
};
charts: {
findings: Chart;
namespaceScope: Chart;
namespaceScope: {
complete: Chart;
preview: Chart;
};
clusterScope: { [key in Status]: number; };
};
details: { title: string; value: string }[]
Expand Down
40 changes: 25 additions & 15 deletions frontend/pages/policies/[source]/[policy].vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
<template>
<v-container fluid class="py-4 px-4 main-height" :key="route.params.policy" v-if="data">
<app-row>
<v-card elevation="2">
<v-toolbar color="header">
<v-toolbar-title>
{{ capilize(route.params.source) }}: {{ data.title }}
</v-toolbar-title>
<template #append>
<form-status-select style="min-width: 200px;" class="mr-2" />
<v-btn variant="text" color="white" prepend-icon="mdi-arrow-left" @click="router.back()">back</v-btn>
</template>
</v-toolbar>
</v-card>
</app-row>
<page-layout :title="`${capilize(route.params.source)}: ${data.title}`"
v-model:kinds="kinds"
v-model:cluster-kinds="clusterKinds"
:source="route.params.source"
v-if="data"
hide-report
>
<template #append>
<form-status-select style="min-width: 200px;" class="mr-2" />
<v-btn variant="text" color="white" prepend-icon="mdi-arrow-left" @click="router.back()">back</v-btn>
</template>

<policy-details :policy="data" v-if="data.showDetails" />

<policy-status-charts :data="data" :policy="route.params.policy" />
<policy-cluster-results :source="route.params.source" :policy="route.params.policy" :status="status" />
<policy-namespace-section :namespaces="data.namespaces" :source="route.params.source" :policy="route.params.policy" :status="status" />
</v-container>
</page-layout>
</template>

<script lang="ts" setup>
import { onChange } from "~/helper/compare";
import { useAPI } from "~/modules/core/composables/api";
import { capilize } from "~/modules/core/layouthHelper";
import { APIFilter } from "~/modules/core/provider/dashboard";
const route = useRoute()
const router = useRouter()
const { load } = useSourceStore(route.params.source)
await load(route.params.source)
const kinds = ref<string[]>([])
const clusterKinds = ref<string[]>([])
const filter = computed(() => ({ kinds: [...kinds.value, ...clusterKinds.value] }))
provide(APIFilter, filter)
const status = computed(() => {
if (!route.query.status) return undefined
Expand All @@ -43,8 +51,10 @@ const { data, refresh } = useAPI((api) => api.policyDetails(
route.params.policy,
route.query.namespace,
status.value,
filter.value.kinds,
));
watch(route, onChange(refresh))
watch(status, onChange(refresh))
watch(filter, onChange(refresh))
</script>
7 changes: 6 additions & 1 deletion frontend/pages/policies/[source]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
:title="capilize(route.params.source)"
v-model:kinds="kinds"
v-model:cluster-kinds="clusterKinds"
:source="route.params.source"
>
<resource-scroller :list="sources">
<template #default="{ item }">
Expand All @@ -16,9 +17,13 @@
import { onChange } from "~/helper/compare";
import { capilize } from "~/modules/core/layouthHelper";
const route = useRoute()
const { load } = useSourceStore(route.params.source)
await load(route.params.source)
const kinds = ref<string[]>([])
const clusterKinds = ref<string[]>([])
const route = useRoute()
const filter = computed(() => ({ sources: [route.params.source], kinds: [...kinds.value, ...clusterKinds.value] }))
Expand Down
3 changes: 3 additions & 0 deletions frontend/pages/policies/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<script setup lang="ts">
import { onChange } from "~/helper/compare";
const { load } = useSourceStore()
await load()
const kinds = ref<string[]>([])
const clusterKinds = ref<string[]>([])
Expand Down

0 comments on commit 0c5897b

Please sign in to comment.