diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go index d6ab76be..16d315c2 100644 --- a/backend/pkg/config/config.go +++ b/backend/pkg/config/config.go @@ -205,10 +205,21 @@ type Boards struct { AccessControl AccessControl `koanf:"accessControl"` } +type Filter struct { + NamespaceKinds []string `koanf:"namespaceKinds"` + ClusterKinds []string `koanf:"clusterKinds"` + Results []string `koanf:"results"` + Severities []string `koanf:"severities"` +} + type CustomBoard struct { Name string `koanf:"name"` AccessControl AccessControl `koanf:"accessControl"` - Namespaces struct { + Filter struct { + Include Filter `koanf:"include"` + } `koanf:"filter"` + Display string `json:"display"` + Namespaces struct { Selector map[string]string `koanf:"selector"` List []string `koanf:"list"` } `koanf:"namespaces"` diff --git a/backend/pkg/config/mapper.go b/backend/pkg/config/mapper.go index 29bc087d..b50c8aa8 100644 --- a/backend/pkg/config/mapper.go +++ b/backend/pkg/config/mapper.go @@ -73,8 +73,10 @@ func MapCustomBoards(customBoards []CustomBoard) map[string]api.CustomBoard { id := slug.Make(c.Name) configs[id] = api.CustomBoard{ - Name: c.Name, - ID: id, + Name: c.Name, + ID: id, + Display: c.Display, + Filter: MapFilter(c.Filter.Include), Namespaces: api.Namespaces{ Selector: c.Namespaces.Selector, List: c.Namespaces.List, @@ -105,3 +107,25 @@ func MapClusterPermissions(c *Config) map[string]auth.Permissions { return permissions } + +func MapFilter(f Filter) api.Includes { + if f.NamespaceKinds == nil { + f.NamespaceKinds = make([]string, 0) + } + if f.ClusterKinds == nil { + f.ClusterKinds = make([]string, 0) + } + if f.Results == nil { + f.Results = make([]string, 0) + } + if f.Severities == nil { + f.Severities = make([]string, 0) + } + + return api.Includes{ + NamespaceKinds: f.NamespaceKinds, + ClusterKinds: f.ClusterKinds, + Results: f.Results, + Severities: f.Severities, + } +} diff --git a/backend/pkg/server/api/handler.go b/backend/pkg/server/api/handler.go index 6bbc5271..f28b3ed2 100644 --- a/backend/pkg/server/api/handler.go +++ b/backend/pkg/server/api/handler.go @@ -199,6 +199,11 @@ func (h *Handler) GetCustomBoard(ctx *gin.Context) { SingleSource: len(sources) == 1, MultipleSource: len(sources) > 1, Namespaces: make([]string, 0), + Display: config.Display, + Severities: config.Filter.Severities, + Status: config.Filter.Results, + NamespaceKinds: config.Filter.NamespaceKinds, + ClusterKinds: config.Filter.ClusterKinds, Charts: service.Charts{ ClusterScope: make(map[string]map[string]int), NamespaceScope: make(map[string]*service.ChartVariants), @@ -210,7 +215,17 @@ func (h *Handler) GetCustomBoard(ctx *gin.Context) { query["namespaces"] = namespaces - dashboard, err := h.service.Dashboard(ctx, ctx.Param("cluster"), sources, namespaces, config.ClusterScope, query) + dashboard, err := h.service.Dashboard(ctx, service.DashboardOptions{ + Cluster: ctx.Param("cluster"), + Sources: sources, + Namespaces: namespaces, + Display: config.Display, + ClusterScope: config.ClusterScope, + Status: config.Filter.Results, + Severities: config.Filter.Severities, + NamespaceKinds: config.Filter.NamespaceKinds, + ClusterKinds: config.Filter.ClusterKinds, + }, query) if err != nil { zap.L().Error("failed to generate dashboard", zap.Error(err)) ctx.AbortWithStatus(http.StatusInternalServerError) @@ -322,7 +337,12 @@ func (h *Handler) Dashboard(ctx *gin.Context) { return } - dashboard, err := h.service.Dashboard(ctx, ctx.Param("cluster"), sources, namespaces, true, ctx.Request.URL.Query()) + dashboard, err := h.service.Dashboard(ctx, service.DashboardOptions{ + Cluster: ctx.Param("cluster"), + Sources: sources, + Namespaces: namespaces, + ClusterScope: true, + }, ctx.Request.URL.Query()) if err != nil { zap.L().Error("failed to generate dashboard", zap.Error(err)) ctx.AbortWithStatus(http.StatusInternalServerError) diff --git a/backend/pkg/server/api/model.go b/backend/pkg/server/api/model.go index a41bd5fb..308140b5 100644 --- a/backend/pkg/server/api/model.go +++ b/backend/pkg/server/api/model.go @@ -27,6 +27,13 @@ type Excludes struct { Severities []string `json:"severities"` } +type Includes struct { + NamespaceKinds []string `json:"namespaceKinds"` + ClusterKinds []string `json:"clusterKinds"` + Results []string `json:"results"` + Severities []string `json:"severities"` +} + type Source struct { Name string `json:"name"` ViewType string `mapstructure:"type"` @@ -58,6 +65,8 @@ type CustomBoard struct { auth.Permissions `json:"-"` Name string `json:"name"` ID string `json:"id"` + Display string `json:"display"` + Filter Includes `json:"filter"` ClusterScope bool `json:"-"` Namespaces Namespaces `json:"-"` Sources Sources `json:"-"` diff --git a/backend/pkg/service/model.go b/backend/pkg/service/model.go index fa461755..1c169fd3 100644 --- a/backend/pkg/service/model.go +++ b/backend/pkg/service/model.go @@ -56,6 +56,7 @@ type Total struct { type Dashboard struct { Title string `json:"title"` Type string `json:"type"` + Display string `json:"display"` FilterSources []string `json:"filterSources,omitempty"` ClusterScope bool `json:"clusterScope"` Sources []string `json:"sources"` @@ -69,6 +70,8 @@ type Dashboard struct { ShowResults []string `json:"showResults"` Status []string `json:"status"` Severities []string `json:"severities"` + NamespaceKinds []string `json:"namespaceKinds"` + ClusterKinds []string `json:"clusterKinds"` } type ResourceDetails struct { @@ -152,3 +155,15 @@ type ExceptionRequest struct { Category string `json:"category"` Policies []ExceptionPolicy `json:"policies"` } + +type DashboardOptions struct { + Status []string + Severities []string + Sources []string + Namespaces []string + NamespaceKinds []string + ClusterKinds []string + Cluster string + Display string + ClusterScope bool +} diff --git a/backend/pkg/service/service.go b/backend/pkg/service/service.go index 76477b97..c703a902 100644 --- a/backend/pkg/service/service.go +++ b/backend/pkg/service/service.go @@ -391,15 +391,15 @@ func (s *Service) ResourceDetails(ctx context.Context, cluster, id string, query }, nil } -func (s *Service) Dashboard(ctx context.Context, cluster string, sources []string, namespaces []string, clusterScope bool, query url.Values) (*Dashboard, error) { - if s.viewType(sources) == model.Severity { - config, ok := s.configs[sources[0]] +func (s *Service) Dashboard(ctx context.Context, o DashboardOptions, query url.Values) (*Dashboard, error) { + if s.viewType(o.Sources) == model.Severity { + config, ok := s.configs[o.Sources[0]] if ok && config.ViewType == model.Severity { - return s.SeverityDashboard(ctx, cluster, sources, namespaces, clusterScope, query) + return s.SeverityDashboard(ctx, o, query) } } - client, err := s.core(cluster) + client, err := s.core(o.Cluster) if err != nil { return nil, err } @@ -407,26 +407,30 @@ func (s *Service) Dashboard(ctx context.Context, cluster string, sources []strin g := &errgroup.Group{} combinedFilter, namespaceFilter, clusterFilter := BuildFilters(query) - combinedFilter.Set("namespaced", strconv.FormatBool(!clusterScope)) + combinedFilter.Set("namespaced", strconv.FormatBool(!o.ClusterScope)) - namespaceResults := make(map[string]core.NamespaceStatusCounts, len(sources)) - clusterResults := make(map[string]map[string]int, len(sources)) - showResults := make([]string, 0, len(sources)) + namespaceResults := make(map[string]core.NamespaceStatusCounts, len(o.Sources)) + clusterResults := make(map[string]map[string]int, len(o.Sources)) + showResults := make([]string, 0, len(o.Sources)) mx := &sync.Mutex{} cmx := &sync.Mutex{} - status := s.filterEnabled(sources, func(c model.SourceConfig) []string { - return c.EnabledResults() - }) + if len(o.Status) == 0 { + o.Status = s.filterEnabled(o.Sources, func(c model.SourceConfig) []string { + return c.EnabledResults() + }) + } - severities := s.filterEnabled(sources, func(c model.SourceConfig) []string { - return c.EnabledSeverities() - }) + if len(o.Severities) == 0 { + o.Severities = s.filterEnabled(o.Sources, func(c model.SourceConfig) []string { + return c.EnabledSeverities() + }) + } - combinedFilter["status"] = status - namespaceFilter["status"] = status - clusterFilter["status"] = status + combinedFilter["status"] = o.Status + namespaceFilter["status"] = o.Status + clusterFilter["status"] = o.Status var findings *core.Findings g.Go(func() error { @@ -436,7 +440,7 @@ func (s *Service) Dashboard(ctx context.Context, cluster string, sources []strin return err }) - for _, source := range sources { + for _, source := range o.Sources { g.Go(func() error { result, err := client.GetNamespaceStatusCounts(ctx, source, namespaceFilter) if err != nil { @@ -458,7 +462,7 @@ func (s *Service) Dashboard(ctx context.Context, cluster string, sources []strin return nil }) - if clusterScope { + if o.ClusterScope { g.Go(func() error { result, err := client.GetClusterStatusCounts(ctx, source, clusterFilter) if err != nil { @@ -478,41 +482,46 @@ func (s *Service) Dashboard(ctx context.Context, cluster string, sources []strin return nil, err } - if namespaces == nil { - namespaces = make([]string, 0) + if o.Namespaces == nil { + o.Namespaces = make([]string, 0) } - singleSource := len(sources) == 1 + singleSource := len(o.Sources) == 1 var exceptions bool if singleSource { - exceptions = s.configs[sources[0]].Exceptions + exceptions = s.configs[o.Sources[0]].Exceptions } var findingChart any - if len(sources) > 1 { + if len(o.Sources) > 1 { findingChart = MapFindingSourcesToFindingCharts(findings) - } else if len(sources) == 1 { - findingChart = MapFindingsToSourceStatusChart(sources[0], findings) + } else if len(o.Sources) == 1 { + findingChart = MapFindingsToSourceStatusChart(o.Sources[0], findings) + } else { + findingChart = MapFindingsToSourceStatusChart("", &core.Findings{}) } return &Dashboard{ Type: model.Status, FilterSources: make([]string, 0), - ClusterScope: clusterScope, - MultipleSource: len(sources) > 1, + ClusterScope: o.ClusterScope, + MultipleSource: len(o.Sources) > 1, SingleSource: singleSource, Exceptions: exceptions, - Sources: sources, - Namespaces: namespaces, + Sources: o.Sources, + Namespaces: o.Namespaces, ShowResults: showResults, SourcesNavi: MapFindingSourcesToSourceItem(findings), - Status: status, - Severities: severities, + Status: o.Status, + Severities: o.Severities, + Display: o.Display, + NamespaceKinds: o.NamespaceKinds, + ClusterKinds: o.ClusterKinds, Charts: Charts{ ClusterScope: clusterResults, Findings: findingChart, - NamespaceScope: MapNamespaceStatusCountsToCharts(namespaceResults, model.Status, status, allStatus), + NamespaceScope: MapNamespaceStatusCountsToCharts(namespaceResults, model.Status, o.Status, allStatus), }, Total: Total{ Count: findings.Total, @@ -521,8 +530,8 @@ func (s *Service) Dashboard(ctx context.Context, cluster string, sources []strin }, nil } -func (s *Service) SeverityDashboard(ctx context.Context, cluster string, sources []string, namespaces []string, clusterScope bool, query url.Values) (*Dashboard, error) { - client, err := s.core(cluster) +func (s *Service) SeverityDashboard(ctx context.Context, o DashboardOptions, query url.Values) (*Dashboard, error) { + client, err := s.core(o.Cluster) if err != nil { return nil, err } @@ -530,26 +539,30 @@ func (s *Service) SeverityDashboard(ctx context.Context, cluster string, sources g := &errgroup.Group{} combinedFilter, namespaceFilter, clusterFilter := BuildFilters(query) - combinedFilter.Set("namespaced", strconv.FormatBool(!clusterScope)) + combinedFilter.Set("namespaced", strconv.FormatBool(!o.ClusterScope)) - namespaceResults := make(map[string]core.NamespaceStatusCounts, len(sources)) - clusterResults := make(map[string]map[string]int, len(sources)) - showResults := make([]string, 0, len(sources)) + namespaceResults := make(map[string]core.NamespaceStatusCounts, len(o.Sources)) + clusterResults := make(map[string]map[string]int, len(o.Sources)) + showResults := make([]string, 0, len(o.Sources)) mx := &sync.Mutex{} cmx := &sync.Mutex{} - status := s.filterEnabled(sources, func(c model.SourceConfig) []string { - return c.EnabledResults() - }) + if len(o.Status) == 0 { + o.Status = s.filterEnabled(o.Sources, func(c model.SourceConfig) []string { + return c.EnabledResults() + }) + } - severities := s.filterEnabled(sources, func(c model.SourceConfig) []string { - return c.EnabledSeverities() - }) + if len(o.Severities) == 0 { + o.Severities = s.filterEnabled(o.Sources, func(c model.SourceConfig) []string { + return c.EnabledSeverities() + }) + } - combinedFilter["severity"] = severities - namespaceFilter["severiy"] = severities - clusterFilter["severity"] = severities + combinedFilter["severity"] = o.Severities + namespaceFilter["severiy"] = o.Severities + clusterFilter["severity"] = o.Severities var findings *core.Findings g.Go(func() error { @@ -559,7 +572,7 @@ func (s *Service) SeverityDashboard(ctx context.Context, cluster string, sources return err }) - for _, source := range sources { + for _, source := range o.Sources { g.Go(func() error { result, err := client.GetNamespaceSeverityCounts(ctx, source, namespaceFilter) if err != nil { @@ -581,7 +594,7 @@ func (s *Service) SeverityDashboard(ctx context.Context, cluster string, sources return nil }) - if clusterScope { + if o.ClusterScope { g.Go(func() error { result, err := client.GetClusterSeverityCounts(ctx, source, clusterFilter) if err != nil { @@ -601,41 +614,46 @@ func (s *Service) SeverityDashboard(ctx context.Context, cluster string, sources return nil, err } - if namespaces == nil { - namespaces = make([]string, 0) + if o.Namespaces == nil { + o.Namespaces = make([]string, 0) } - singleSource := len(sources) == 1 + singleSource := len(o.Sources) == 1 var exceptions bool if singleSource { - exceptions = s.configs[sources[0]].Exceptions + exceptions = s.configs[o.Sources[0]].Exceptions } var findingChart any - if len(sources) > 1 { + if len(o.Sources) > 1 { findingChart = MapFindingSourcesToFindingCharts(findings) - } else if len(sources) == 1 { - findingChart = MapSeverityFindingsToSourceStatusChart(sources[0], findings) + } else if len(o.Sources) == 1 { + findingChart = MapSeverityFindingsToSourceStatusChart(o.Sources[0], findings) + } else { + findingChart = MapSeverityFindingsToSourceStatusChart("", &core.Findings{}) } return &Dashboard{ Type: model.Severity, FilterSources: make([]string, 0), - ClusterScope: clusterScope, - MultipleSource: len(sources) > 1, + ClusterScope: o.ClusterScope, + MultipleSource: len(o.Sources) > 1, SingleSource: singleSource, Exceptions: exceptions, - Sources: sources, - Namespaces: namespaces, + Sources: o.Sources, + Namespaces: o.Namespaces, ShowResults: showResults, SourcesNavi: MapFindingSourcesToSourceItem(findings), - Status: status, - Severities: severities, + Status: o.Status, + Severities: o.Severities, + Display: o.Display, + NamespaceKinds: o.NamespaceKinds, + ClusterKinds: o.ClusterKinds, Charts: Charts{ ClusterScope: clusterResults, Findings: findingChart, - NamespaceScope: MapNamespaceStatusCountsToCharts(namespaceResults, model.Severity, severities, allSeverities), + NamespaceScope: MapNamespaceStatusCountsToCharts(namespaceResults, model.Severity, o.Severities, allSeverities), }, Total: Total{ Count: findings.Total, diff --git a/frontend/bun.lockb b/frontend/bun.lockb index a1e69bef..f34541f2 100755 Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ diff --git a/frontend/composables/infinite.ts b/frontend/composables/infinite.ts index 74afea1a..aeeda378 100644 --- a/frontend/composables/infinite.ts +++ b/frontend/composables/infinite.ts @@ -1,7 +1,7 @@ import type { Ref, UnwrapRef } from "vue"; import type { UnwrapRefSimple } from "@vue/reactivity"; -export const useInfinite = (list: Ref, defaultLoadings = 3) => { +export const useInfinite = (list: Ref, total: number, defaultLoadings = 3) => { const loaded = ref([]) const index = ref(0) @@ -20,9 +20,8 @@ export const useInfinite = (list: Ref, defaultLoadings = 3) => { } if (oldLength > 0 && oldLength < length) { - loaded.value = l.slice(0, oldLength + 1) as UnwrapRefSimple[] - index.value = oldLength + 1 - + loaded.value = [...l] + index.value = length return } @@ -60,7 +59,6 @@ export const useInfinite = (list: Ref, defaultLoadings = 3) => { } } - return { load, loaded } } diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index 063e7f20..41324ad4 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -13,9 +13,8 @@ +