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 | ❌ | ✅ | ❌ | ✅ |