diff --git a/dashboard/lib/dropdowns.py b/dashboard/lib/dropdowns.py index 3e92759042..421801bc27 100644 --- a/dashboard/lib/dropdowns.py +++ b/dashboard/lib/dropdowns.py @@ -312,3 +312,28 @@ def create_table_row_dropdown(table_rows: list) -> html.Div: ), ], ) + + +def create_category_dropdown(categories: list) -> html.Div: + """ + Dropdown to select the category. + Args: + categories (list): List of categories. + Returns: + html.Div: Dropdown to select the category. + """ + return html.Div( + [ + html.Label( + "Category:", className="text-prowler-stone-900 font-bold text-sm" + ), + dcc.Dropdown( + id="category-filter", + options=[{"label": i, "value": i} for i in categories], + value=["All"], + clearable=False, + multi=True, + style={"color": "#000000"}, + ), + ], + ) diff --git a/dashboard/lib/layouts.py b/dashboard/lib/layouts.py index 9df7cefb16..930432b6c4 100644 --- a/dashboard/lib/layouts.py +++ b/dashboard/lib/layouts.py @@ -12,6 +12,7 @@ def create_layout_overview( provider_dropdown: html.Div, table_row_dropdown: html.Div, status_dropdown: html.Div, + category_dropdown: html.Div, table_div_header: html.Div, amount_providers: int, ) -> html.Div: @@ -51,8 +52,9 @@ def create_layout_overview( html.Div([service_dropdown], className=""), html.Div([provider_dropdown], className=""), html.Div([status_dropdown], className=""), + html.Div([category_dropdown], className=""), ], - className="grid gap-x-4 mb-[30px] sm:grid-cols-2 lg:grid-cols-4", + className="grid gap-x-4 mb-[30px] sm:grid-cols-2 lg:grid-cols-5", ), html.Div( [ diff --git a/dashboard/pages/overview.py b/dashboard/pages/overview.py index 9bf44ac8ac..ada06b8282 100644 --- a/dashboard/pages/overview.py +++ b/dashboard/pages/overview.py @@ -35,6 +35,7 @@ from dashboard.config import ( from dashboard.lib.cards import create_provider_card from dashboard.lib.dropdowns import ( create_account_dropdown, + create_category_dropdown, create_date_dropdown, create_provider_dropdown, create_region_dropdown, @@ -343,6 +344,18 @@ else: status = [x for x in status if str(x) != "nan" and x.__class__.__name__ == "str"] status_dropdown = create_status_dropdown(status) + + # Create the category dropdown + categories = [] + if "CATEGORIES" in data.columns: + for cat_list in data["CATEGORIES"].dropna().unique(): + if cat_list and str(cat_list) != "nan": + for cat in str(cat_list).split(","): + cat = cat.strip() + if cat and cat not in categories: + categories.append(cat) + categories = ["All"] + sorted(categories) + category_dropdown = create_category_dropdown(categories) table_div_header = [] table_div_header.append( html.Div( @@ -504,6 +517,7 @@ else: provider_dropdown, table_row_dropdown, status_dropdown, + category_dropdown, table_div_header, len(data["PROVIDER"].unique()), ) @@ -540,6 +554,8 @@ else: Output("table-rows", "options"), Output("status-filter", "value"), Output("status-filter", "options"), + Output("category-filter", "value"), + Output("category-filter", "options"), Output("aws_card", "n_clicks"), Output("azure_card", "n_clicks"), Output("gcp_card", "n_clicks"), @@ -557,6 +573,7 @@ else: Input("provider-filter", "value"), Input("table-rows", "value"), Input("status-filter", "value"), + Input("category-filter", "value"), Input("search-input", "value"), Input("aws_card", "n_clicks"), Input("azure_card", "n_clicks"), @@ -582,6 +599,7 @@ def filter_data( provider_values, table_row_values, status_values, + category_values, search_value, aws_clicks, azure_clicks, @@ -965,6 +983,41 @@ def filter_data( status_filter_options = ["All"] + list(filtered_data["STATUS"].unique()) + # Filter Category + if "CATEGORIES" in filtered_data.columns: + if category_values == ["All"]: + updated_category_values = None + elif "All" in category_values and len(category_values) > 1: + category_values.remove("All") + updated_category_values = category_values + elif len(category_values) == 0: + updated_category_values = None + category_values = ["All"] + else: + updated_category_values = category_values + + if updated_category_values: + filtered_data = filtered_data[ + filtered_data["CATEGORIES"].apply( + lambda x: any( + cat.strip() in updated_category_values + for cat in str(x).split(",") + if str(x) != "nan" + ) + ) + ] + + category_filter_options = ["All"] + for cat_list in filtered_data["CATEGORIES"].dropna().unique(): + if cat_list and str(cat_list) != "nan": + for cat in str(cat_list).split(","): + cat = cat.strip() + if cat and cat not in category_filter_options: + category_filter_options.append(cat) + category_filter_options = sorted(category_filter_options) + else: + category_filter_options = ["All"] + if len(filtered_data_sp) == 0: fig = px.pie() fig.update_layout( @@ -1512,6 +1565,8 @@ def filter_data( table_row_options, status_values, status_filter_options, + category_values, + category_filter_options, aws_clicks, azure_clicks, gcp_clicks, @@ -1549,6 +1604,8 @@ def filter_data( table_row_options, status_values, status_filter_options, + category_values, + category_filter_options, aws_clicks, azure_clicks, gcp_clicks, diff --git a/docs/user-guide/cli/tutorials/dashboard.mdx b/docs/user-guide/cli/tutorials/dashboard.mdx index 01c60b29e9..8c819743fe 100644 --- a/docs/user-guide/cli/tutorials/dashboard.mdx +++ b/docs/user-guide/cli/tutorials/dashboard.mdx @@ -1,5 +1,5 @@ --- -title: 'Dashboard' +title: "Dashboard" --- Prowler allows you to run your own local dashboards using the csv outputs provided by Prowler @@ -34,26 +34,30 @@ The overview page provides a full impression of your findings obtained from Prow This page allows for multiple functions: -* Apply filters: +- Apply filters: - * Assesment Date - * Account - * Region - * Severity - * Service - * Status + - Assesment Date + - Account + - Region + - Severity + - Service + - Provider + - Status + - Category -* See which files has been scanned to generate the dashboard by placing your mouse on the `?` icon: +- See which files has been scanned to generate the dashboard by placing your mouse on the `?` icon: - + {" "} + -* Download the `Top Findings by Severity` table using the button `DOWNLOAD THIS TABLE AS CSV` or `DOWNLOAD THIS TABLE AS XLSX` +- Download the `Top Findings by Severity` table using the button `DOWNLOAD THIS TABLE AS CSV` or `DOWNLOAD THIS TABLE AS XLSX` -* Click the provider cards to filter by provider. +- Click the provider cards to filter by provider. -* On the dropdowns under `Top Findings by Severity` you can apply multiple sorts to see the information, also you will get a detailed view of each finding using the dropdowns: +- On the dropdowns under `Top Findings by Severity` you can apply multiple sorts to see the information, also you will get a detailed view of each finding using the dropdowns: - + {" "} + ## Compliance Page @@ -110,17 +114,16 @@ To change the path, modify the values `folder_path_overview` or `folder_path_com If you have any issue related with dashboards, check that the output path where the dashboard is getting the outputs is correct. - ## Output Support Prowler dashboard supports the detailed outputs: -| Provider| V3| V4| COMPLIANCE-V3| COMPLIANCE-V4 -|----------|----------|----------|----------|---------- -| AWS| ✅| ✅| ✅| ✅ -| Azure| ❌| ✅| ❌| ✅ -| Kubernetes| ❌| ✅| ❌| ✅ -| GCP| ❌| ✅| ❌| ✅ -| M365| ❌| ✅| ❌| ✅ -| GitHub| ❌| ✅| ❌| ✅ +| Provider | V3 | V4 | COMPLIANCE-V3 | COMPLIANCE-V4 | +| ---------- | --- | --- | ------------- | ------------- | +| AWS | ✅ | ✅ | ✅ | ✅ | +| Azure | ❌ | ✅ | ❌ | ✅ | +| Kubernetes | ❌ | ✅ | ❌ | ✅ | +| GCP | ❌ | ✅ | ❌ | ✅ | +| M365 | ❌ | ✅ | ❌ | ✅ | +| GitHub | ❌ | ✅ | ❌ | ✅ |