Skip to content

Commit

Permalink
Update UI
Browse files Browse the repository at this point in the history
Signed-off-by: Frank Jogeleit <frank.jogeleit@lovoo.com>
  • Loading branch information
Frank Jogeleit committed Feb 7, 2024
1 parent b4524b2 commit d88bb06
Show file tree
Hide file tree
Showing 21 changed files with 164 additions and 145 deletions.
1 change: 1 addition & 0 deletions backend/pkg/server/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func (h *Handler) GetCustomBoard(ctx *gin.Context) {
}

dashboard.FilterSources = query["sources"]
dashboard.Title = config.Name

ctx.JSON(http.StatusOK, dashboard)
}
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/service/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Total struct {
}

type Dashboard struct {
Title string `json:"title"`
FilterSources []string `json:"filterSources,omitempty"`
ClusterScope bool `json:"clusterScope"`
Sources []string `json:"sources"`
Expand Down
4 changes: 2 additions & 2 deletions charts/ui/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: ui
description: Policy Reporter UI
type: application
version: 0.0.14
appVersion: "2.0.0-alpha.9"
version: 0.0.15
appVersion: "2.0.0-alpha.10"

icon: https://github.com/kyverno/kyverno/raw/main/img/logo.png
home: https://kyverno.github.io/policy-reporter-ui
Expand Down
2 changes: 1 addition & 1 deletion charts/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Policy Reporter UI

![Version: 0.0.10](https://img.shields.io/badge/Version-0.0.10-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.0.0-alpha.5](https://img.shields.io/badge/AppVersion-2.0.0--alpha.5-informational?style=flat-square)
![Version: 0.0.15](https://img.shields.io/badge/Version-0.0.15-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.0.0-alpha.10](https://img.shields.io/badge/AppVersion-2.0.0--alpha.10-informational?style=flat-square)

## Documentation

Expand Down
54 changes: 54 additions & 0 deletions frontend/composables/source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
type Store = {
kinds: { namespaced: string[]; cluster: string[] },
namespaces: string[]
categories: string[]
}

const sources: { [source: string]: Store } = {}

export const useSourceStore = (source?: string) => {
const key = source || 'global'

if (!sources[key]) {
sources[key] = reactive({
kinds: { namespaced: [], cluster: [] },
namespaces: [],
categories: [],
})
}

const store = sources[key]

const loading = ref(false)
const error = ref<Error | null>(null)

const load = () => {
loading.value = true
error.value = null

return Promise.all([
callAPI(api => api.namespaces({ sources: source ? [source] : undefined })),
callAPI(api => api.namespacedKinds(source)),
callAPI(api => api.clusterKinds(source)),
callAPI(api => api.categoryTree(undefined, { sources: source ? [source] : undefined })),
]).then(([namespaces, nsKinds, clusterKinds, categoryTrees]) => {
store.kinds.namespaced = nsKinds || []
store.kinds.cluster = clusterKinds || []
store.namespaces = namespaces || []
store.categories = (categoryTrees || []).reduce<string[]>((categories, source) => {
return [...categories, ...source.categories.map(c => c.name)]
}, [])
}).catch((err) => {
error.value = err
}).finally(() => {
loading.value = false
})
}

return {
store,
loading,
error,
load
}
}
4 changes: 2 additions & 2 deletions frontend/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@
</template>

<script setup lang="ts">
import type { LayoutConfig } from "~/modules/core/types";
import { useTheme } from "vuetify";
import type { LayoutConfig } from "~/modules/core/types";
const drawer = ref(true)
const { data: layout } = useAPI((api) => api.layout(), { default: (): LayoutConfig => ({ sources: [], customBoards: [] }) })
const { data: layout } = useAPI((api) => api.layout(), { default: (): LayoutConfig => ({ sources: [], customBoards: [], policies: [] }) })
const theme = useTheme()
Expand Down
63 changes: 35 additions & 28 deletions frontend/modules/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
type ResourceDetails, type SourceDetails, type PolicyDetails
} from './types'

import type { NitroFetchOptions, NitroFetchRequest } from "nitropack";

type APIConfig = { baseURL: string; prefix?: string; };

export const cluster = ref('default')
Expand All @@ -35,115 +37,115 @@ export class CoreAPI {
}

profile () {
return $fetch<Profile>('/profile', { baseURL: this.baseURL })
return exec<Profile>('/profile', { baseURL: this.baseURL })
}

layout () {
return $fetch<LayoutConfig>(`/api/config/${this.cluster}/layout`, { baseURL: this.baseURL })
return exec<LayoutConfig>(`/api/config/${this.cluster}/layout`, { baseURL: this.baseURL })
}

dashboard <T extends Boolean>(filter?: Filter) {
return $fetch<Dashboard<T>>(`/api/config/${this.cluster}/dashboard`, { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
return exec<Dashboard<T>>(`/api/config/${this.cluster}/dashboard`, { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
}

customBoard <T extends Boolean>(id: string, filter?: Filter) {
return $fetch<Dashboard<T>>(`/api/config/${this.cluster}/custom-board/${id}`, { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
return exec<Dashboard<T>>(`/api/config/${this.cluster}/custom-board/${id}`, { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
}

resource (id: string, filter?: Filter) {
return $fetch<ResourceDetails>(`/api/config/${this.cluster}/resource/${id}`, { baseURL: this.baseURL, params: filter })
return exec<ResourceDetails>(`/api/config/${this.cluster}/resource/${id}`, { baseURL: this.baseURL, params: filter })
}

policySources (filter?: Filter) {
return $fetch<SourceDetails[]>(`/api/config/${this.cluster}/policy-sources`, { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
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) {
return $fetch<PolicyDetails>(`/api/config/${this.cluster}/${source}/policy/details`, { baseURL: this.baseURL, params: applyExcludes({ policies: [policy], namespace }, [...this.nsExcludes, ...this.clusterExcludes]) })
return exec<PolicyDetails>(`/api/config/${this.cluster}/${source}/policy/details`, { baseURL: this.baseURL, params: applyExcludes({ policies: [policy], namespace }, [...this.nsExcludes, ...this.clusterExcludes]) })
}

policyHTMLReport (source: string, filter: { namespaces: string[]; categories: string[]; kinds: string[]; clusterScope: boolean; }) {
return $fetch<BlobPart>(`/api/config/${this.cluster}/${source}/policy-report`, { baseURL: this.baseURL, params: filter, responseType: 'blob' })
return exec<BlobPart>(`/api/config/${this.cluster}/${source}/policy-report`, { baseURL: this.baseURL, params: filter, responseType: 'blob' })
}

namespaceHTMLReport (source: string, filter: { namespaces: string[]; categories: string[]; kinds: string[]; }) {
return $fetch<BlobPart>(`/api/config/${this.cluster}/${source}/namespace-report`, { baseURL: this.baseURL, params: filter, responseType: 'blob' })
return exec<BlobPart>(`/api/config/${this.cluster}/${source}/namespace-report`, { baseURL: this.baseURL, params: filter, responseType: 'blob' })
}

policies (source: string, filter?: Filter) {
return $fetch<{ [category: string]: PolicyResult[] }>(`/api/config/${this.cluster}/${source}/policies`, { baseURL: this.baseURL, params: applyExcludes(filter, this.nsExcludes)})
return exec<{ [category: string]: PolicyResult[] }>(`/api/config/${this.cluster}/${source}/policies`, { baseURL: this.baseURL, params: applyExcludes(filter, this.nsExcludes)})
}

config () {
return $fetch<Config>('/api/config', { baseURL: this.baseURL })
return exec<Config>('/api/config', { baseURL: this.baseURL })
}

targets () {
return $fetch<{ [type: string]: Target[] }>(`/proxy/${this.cluster}/core/v2/targets`, { baseURL: this.baseURL })
return exec<{ [type: string]: Target[] }>(`/proxy/${this.cluster}/core/v2/targets`, { baseURL: this.baseURL })
}

namespaces (filter?: Filter) {
return $fetch<string[]>(`/proxy/${this.cluster}/core/v1/namespaces`, { baseURL: this.baseURL, params: { ...filter } })
return exec<string[]>(`/proxy/${this.cluster}/core/v1/namespaces`, { baseURL: this.baseURL, params: { ...filter } })
}

namespacedKinds (source?: string) {
return $fetch<string[]>('/proxy/'+this.cluster+'/core/v1/namespaced-resources/kinds', { baseURL: this.baseURL, params: { sources: source ? [source] : undefined } })
return exec<string[]>('/proxy/'+this.cluster+'/core/v1/namespaced-resources/kinds', { baseURL: this.baseURL, params: { sources: source ? [source] : undefined } })
}

namespacedStatusCount (source: string, filter?: Filter) {
return $fetch<NamespaceStatusCount>(`/proxy/${this.cluster}/core/v2/namespace-scoped/${source}/status-counts`, { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes) } })
return exec<NamespaceStatusCount>(`/proxy/${this.cluster}/core/v2/namespace-scoped/${source}/status-counts`, { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes) } })
}

namespacedResults (filter?: Filter, pagination?: Pagination) {
return $fetch<ResultList>('/proxy/'+this.cluster+'/core/v1/namespaced-resources/results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes), ...pagination } })
return exec<ResultList>('/proxy/'+this.cluster+'/core/v1/namespaced-resources/results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes), ...pagination } })
}

statusCount (source: string, filter?: Filter) {
return $fetch<{ [status in Status]: number }>(`/proxy/${this.cluster}/core/v2/namespace-scoped/${source}/status-counts`, { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.clusterExcludes) } })
return exec<{ [status in Status]: number }>(`/proxy/${this.cluster}/core/v2/namespace-scoped/${source}/status-counts`, { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.clusterExcludes) } })
}

clusterKinds (source?: string) {
return $fetch<string[]>('/proxy/'+this.cluster+'/core/v1/cluster-resources/kinds', { baseURL: this.baseURL, params: { sources: source ? [source] : undefined } })
return exec<string[]>('/proxy/'+this.cluster+'/core/v1/cluster-resources/kinds', { baseURL: this.baseURL, params: { sources: source ? [source] : undefined } })
}

clusterResults (filter?: Filter, pagination?: Pagination) {
return $fetch<ResultList>('/proxy/'+this.cluster+'/core/v1/cluster-resources/results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes), ...pagination } })
return exec<ResultList>('/proxy/'+this.cluster+'/core/v1/cluster-resources/results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes), ...pagination } })
}

resultsWithoutResources (filter?: Filter, pagination?: Pagination) {
return $fetch<ResultList>('/proxy/'+this.cluster+'/core/v2/results-without-resources', { baseURL: this.baseURL, params: { ...filter, ...pagination } })
return exec<ResultList>('/proxy/'+this.cluster+'/core/v2/results-without-resources', { baseURL: this.baseURL, params: { ...filter, ...pagination } })
}

countFindings (filter?: Filter) {
return $fetch<FindingCounts>('/proxy/'+this.cluster+'/core/v2/findings', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes) } })
return exec<FindingCounts>('/proxy/'+this.cluster+'/core/v2/findings', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes) } })
}

namespacedResourceResults (filter?: Filter, pagination?: Pagination) {
return $fetch<ResourceResultList>('/proxy/'+this.cluster+'/core/v2/namespace-scoped/resource-results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes), ...pagination } })
return exec<ResourceResultList>('/proxy/'+this.cluster+'/core/v2/namespace-scoped/resource-results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.nsExcludes), ...pagination } })
}

clusterResourceResults (filter?: Filter, pagination?: Pagination) {
return $fetch<ResourceResultList>('/proxy/'+this.cluster+'/core/v2/cluster-scoped/resource-results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.clusterExcludes), ...pagination } })
return exec<ResourceResultList>('/proxy/'+this.cluster+'/core/v2/cluster-scoped/resource-results', { baseURL: this.baseURL, params: { ...applyExcludes(filter, this.clusterExcludes), ...pagination } })
}

resourceResults (id: string, filter?: Filter) {
return $fetch<ResourceResult[]>(`/proxy/${this.cluster}/core/v2/resource/${id}/resource-results`, { baseURL: this.baseURL, params: filter })
return exec<ResourceResult[]>(`/proxy/${this.cluster}/core/v2/resource/${id}/resource-results`, { baseURL: this.baseURL, params: filter })
}

resourceStatusCount (id: string, filter?: Filter) {
return $fetch<ResourceStatusCount[]>(`/proxy/${this.cluster}/core/v2/resource/${id}/status-counts`, { baseURL: this.baseURL, params: filter })
return exec<ResourceStatusCount[]>(`/proxy/${this.cluster}/core/v2/resource/${id}/status-counts`, { baseURL: this.baseURL, params: filter })
}

results (id: string, pagination?: Pagination, filter?: Filter) {
return $fetch<ResultList>(`/proxy/${this.cluster}/core/v2/resource/${id}/results`, { baseURL: this.baseURL, params: { ...pagination, ...filter } })
return exec<ResultList>(`/proxy/${this.cluster}/core/v2/resource/${id}/results`, { baseURL: this.baseURL, params: { ...pagination, ...filter } })
}

sources (filter?: Filter) {
return $fetch<string[]>('/proxy/'+this.cluster+'/core/v2/sources', { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
return exec<string[]>('/proxy/'+this.cluster+'/core/v2/sources', { baseURL: this.baseURL, params: applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) })
}

categoryTree (id?: string, filter?: Filter) {
return $fetch<Source[]>('/proxy/'+this.cluster+'/core/v2/sources/categories', { baseURL: this.baseURL, params: { id, ...applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) } })
return exec<Source[]>('/proxy/'+this.cluster+'/core/v2/sources/categories', { baseURL: this.baseURL, params: { id, ...applyExcludes(filter, [...this.nsExcludes, ...this.clusterExcludes]) } })
}

setPrefix (prefix: string): void {
Expand All @@ -159,6 +161,11 @@ export class CoreAPI {

export const create = (config: APIConfig): CoreAPI => new CoreAPI(config)


const exec = <T>(api: string, opts: NitroFetchOptions<NitroFetchRequest>): Promise<T> => {
return $fetch<T>(api, opts)
}

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

Expand Down
14 changes: 2 additions & 12 deletions frontend/modules/core/components/form/CategorySelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
<v-autocomplete
multiple
clearable
:items="items as string[]"
:loading="pending as boolean"
:items="store.categories"
variant="outlined"
hide-details
label="Categories"
Expand All @@ -26,17 +25,8 @@
const props = defineProps<{ source: string; modelValue: string[] }>();
const selected = ref<string[]>(props.modelValue);
const loading = ref<boolean>(true);
const { data: items, pending } = useAPI(
(api) => api.categoryTree(undefined, { sources: [props.source] }).then(list => list.length ? list[0].categories.map(c => c.name) : []),
{
default: () => [],
finally: () => {
loading.value = false;
},
}
);
const { store } = useSourceStore(props.source)
const emit = defineEmits<{ 'update:modelValue': [kinds: string[]] }>()
Expand Down
19 changes: 4 additions & 15 deletions frontend/modules/core/components/form/ClusterKindAutocomplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
multiple
clearable
density="compact"
:items="items as string[]"
:items="store.kinds.cluster"
variant="outlined"
hide-details
label="Cluster Kinds"
closable-chips
:model-value="selected"
@update:model-value="input"
v-bind="$attrs"
v-if="items.length"
v-if="store.kinds.cluster.length"
>
<template v-slot:selection="{ item, index }">
<v-chip v-if="index < 2">
Expand All @@ -28,23 +28,12 @@
const props = defineProps<{ source?: string; modelValue: string[] }>();
const selected = ref<string[]>(props.modelValue);
const loading = ref<boolean>(true);
const { data: items } = useAPI(
($coreAPI) => {
return $coreAPI.clusterKinds(props.source)
},
{
default: () => [],
finally: () => {
loading.value = false;
},
}
);
const { store } = useSourceStore(props.source)
const input = defineRouteQuery('cluster-kinds', selected);
watch(items, (current) => {
watch(store.kinds.cluster, (current) => {
input(selected.value.filter((s) => current.includes(s)));
});
Expand Down
15 changes: 3 additions & 12 deletions frontend/modules/core/components/form/KindAutocomplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
multiple
clearable
density="compact"
:items="items as string[]"
:items="store.kinds.namespaced"
variant="outlined"
hide-details
label="Kinds"
Expand All @@ -27,21 +27,12 @@
const props = defineProps<{ source?: string; modelValue: string[] }>();
const selected = ref<string[]>(props.modelValue);
const loading = ref<boolean>(true);
const { data: items } = useAPI(
(api) => api.namespacedKinds(props.source),
{
default: () => [],
finally: () => {
loading.value = false;
},
}
);
const { store } = useSourceStore(props.source)
const input = defineRouteQuery('kinds', selected);
watch(items, (current) => {
watch(store.kinds.namespaced, (current) => {
input(selected.value.filter((s) => current.includes(s)));
});
Expand Down
Loading

0 comments on commit d88bb06

Please sign in to comment.