diff --git a/dashboard/__main__.py b/dashboard/__main__.py index 1c2b93c1f8..0cdeb66430 100644 --- a/dashboard/__main__.py +++ b/dashboard/__main__.py @@ -35,7 +35,7 @@ dashboard = dash.Dash( # Logo prowler_logo = html.Img( - src="https://prowler.com/logo-dashboard.png", alt="Prowler Logo" + src="https://prowler.com/wp-content/uploads/logo-dashboard.png", alt="Prowler Logo" ) menu_icons = { @@ -119,7 +119,9 @@ dashboard.layout = html.Div( # Placeholder for dynamic navigation bar html.Div( [ - html.Div(id="navigation-bar", className="bg-prowler-stone-900"), + html.Div( + id="navigation-bar", className="bg-prowler-stone-900 min-w-36 z-10" + ), html.Div( [ dash.page_container, @@ -149,22 +151,24 @@ def update_nav_bar(pathname): [html.Ul(generate_nav_links(pathname), className="")], className="flex flex-col gap-y-6", ), - html.A( - [ - html.Span( - [ - html.Img(src="assets/favicon.ico", className="w-5"), - "Subscribe to prowler SaaS", - ], - className="flex items-center gap-x-3", - ), - ], - href="https://prowler.com/", - target="_blank", - className="block mt-300 px-3 py-3 uppercase text-xs hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime", - ), html.Nav( - [html.Ul(generate_help_menu(), className="")], + [ + html.A( + [ + html.Span( + [ + html.Img(src="assets/favicon.ico", className="w-5"), + "Subscribe to prowler SaaS", + ], + className="flex items-center gap-x-3", + ), + ], + href="https://prowler.com/", + target="_blank", + className="block p-3 uppercase text-xs hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime", + ), + html.Ul(generate_help_menu(), className=""), + ], className="flex flex-col gap-y-6 mt-auto", ), ], diff --git a/dashboard/assets/styles/dist/output.css b/dashboard/assets/styles/dist/output.css index bfd214fc06..e3f2485c66 100644 --- a/dashboard/assets/styles/dist/output.css +++ b/dashboard/assets/styles/dist/output.css @@ -660,6 +660,10 @@ video { position: relative; } +.z-10 { + z-index: 10; +} + .col-span-11 { grid-column: span 11 / span 11; } @@ -729,6 +733,14 @@ video { display: inline-flex; } +.min-w-36 { + min-width: 9rem; +} + +.min-w-44 { + min-width: 11rem; +} + .table { display: table; } @@ -894,6 +906,10 @@ video { background-image: linear-gradient(127.43deg, #F1F5F8 -177.68%, #636c78 87.35%); } +.p-3 { + padding: 0.75rem; +} + .p-2 { padding: 0.5rem; } diff --git a/dashboard/pages/overview.py b/dashboard/pages/overview.py index 9ec765e3c9..115d6d4c67 100644 --- a/dashboard/pages/overview.py +++ b/dashboard/pages/overview.py @@ -61,7 +61,8 @@ def load_csv_files(csv_files): for file in csv_files: df = pd.read_csv(file, sep=";", on_bad_lines="skip") if "CHECK_ID" in df.columns: - dfs.append(df.astype(str)) + if "TIMESTAMP" in df.columns or df["PROVIDER"].unique() == "aws": + dfs.append(df.astype(str)) # Handle the case where there are no files try: data = pd.concat(dfs, ignore_index=True) @@ -101,35 +102,43 @@ else: data["ASSESSMENT_START_TIME"] = data["ASSESSMENT_START_TIME"].str.replace( "T", " " ) - # for each row, we are going to take the ASSESMENT_START_TIME if is not null and put it in the TIMESTAMP column + data.rename(columns={"ASSESSMENT_START_TIME": "TIMESTAMP_AUX"}, inplace=True) + # Unify the columns data["TIMESTAMP"] = data.apply( lambda x: ( - x["ASSESSMENT_START_TIME"] - if pd.isnull(x["TIMESTAMP"]) - else x["TIMESTAMP"] + x["TIMESTAMP_AUX"] if pd.isnull(x["TIMESTAMP"]) else x["TIMESTAMP"] ), axis=1, ) if "ACCOUNT_ID" in data.columns: + data.rename(columns={"ACCOUNT_ID": "ACCOUNT_UID_AUX"}, inplace=True) data["ACCOUNT_UID"] = data.apply( lambda x: ( - x["ACCOUNT_ID"] if pd.isnull(x["ACCOUNT_UID"]) else x["ACCOUNT_UID"] + x["ACCOUNT_UID_AUX"] + if pd.isnull(x["ACCOUNT_UID"]) + else x["ACCOUNT_UID"] ), axis=1, ) # Rename the column RESOURCE_ID to RESOURCE_UID if "RESOURCE_ID" in data.columns: + data.rename(columns={"RESOURCE_ID": "RESOURCE_UID_AUX"}, inplace=True) data["RESOURCE_UID"] = data.apply( lambda x: ( - x["RESOURCE_ID"] if pd.isnull(x["RESOURCE_UID"]) else x["RESOURCE_UID"] + x["RESOURCE_UID_AUX"] + if pd.isnull(x["RESOURCE_UID"]) + else x["RESOURCE_UID"] ), axis=1, ) # Rename the column "SUBSCRIPTION" to "ACCOUNT_UID" if "SUBSCRIPTION" in data.columns: + data.rename(columns={"SUBSCRIPTION": "ACCOUNT_UID_AUX"}, inplace=True) data["ACCOUNT_UID"] = data.apply( lambda x: ( - x["SUBSCRIPTION"] if pd.isnull(x["ACCOUNT_UID"]) else x["ACCOUNT_UID"] + x["ACCOUNT_UID_AUX"] + if pd.isnull(x["ACCOUNT_UID"]) + else x["ACCOUNT_UID"] ), axis=1, ) @@ -252,8 +261,14 @@ else: Input("cloud-account-filter", "value"), Input("region-filter", "value"), Input("report-date-filter", "value"), + Input("download_link", "n_clicks"), ) -def filter_data(cloud_account_values, region_account_values, assessment_value): +def filter_data( + cloud_account_values, region_account_values, assessment_value, n_clicks +): + # Use n_clicks for vulture + n_clicks = n_clicks + # Filter the data filtered_data = data.copy() # For all the data, we will add to the status column the value 'MUTED (FAIL)' and 'MUTED (PASS)' depending on the value of the column 'STATUS' and 'MUTED' if "MUTED" in filtered_data.columns: @@ -310,28 +325,21 @@ def filter_data(cloud_account_values, region_account_values, assessment_value): for file in csv_files: df = pd.read_csv(file, sep=";", on_bad_lines="skip") if "CHECK_ID" in df.columns: - # This handles the case where we are using v3 outputs - if "TIMESTAMP" not in df.columns: - # Rename the column 'ASSESSMENT_START_TIME' to 'TIMESTAMP' - df.rename(columns={"ASSESSMENT_START_TIME": "TIMESTAMP"}, inplace=True) - df["TIMESTAMP"] = df["TIMESTAMP"].str.replace("T", " ") - elif "ASSESSMENT_START_TIME" in df.columns: - df["ASSESSMENT_START_TIME"] = df["ASSESSMENT_START_TIME"].str.replace( - "T", " " - ) - # for each row, we are going to take the ASSESMENT_START_TIME if is not null and put it in the TIMESTAMP column - df["TIMESTAMP"] = df.apply( - lambda x: ( - x["ASSESSMENT_START_TIME"] - if pd.isnull(x["TIMESTAMP"]) - else x["TIMESTAMP"] - ), - axis=1, - ) - df["TIMESTAMP"] = pd.to_datetime(df["TIMESTAMP"]) - df["TIMESTAMP"] = df["TIMESTAMP"].dt.strftime("%Y-%m-%d") - if df["TIMESTAMP"][0] == updated_assessment_value: - list_files.append(file) + if "TIMESTAMP" in df.columns or df["PROVIDER"].unique() == "aws": + # This handles the case where we are using v3 outputs + if "TIMESTAMP" not in df.columns and df["PROVIDER"].unique() == "aws": + # Rename the column 'ASSESSMENT_START_TIME' to 'TIMESTAMP' + df["ASSESSMENT_START_TIME"] = df[ + "ASSESSMENT_START_TIME" + ].str.replace("T", " ") + df.rename( + columns={"ASSESSMENT_START_TIME": "TIMESTAMP"}, inplace=True + ) + df["TIMESTAMP"] = df["TIMESTAMP"].str.replace("T", " ") + df["TIMESTAMP"] = pd.to_datetime(df["TIMESTAMP"]) + df["TIMESTAMP"] = df["TIMESTAMP"].dt.strftime("%Y-%m-%d") + if df["TIMESTAMP"][0] == updated_assessment_value: + list_files.append(file) # append all the names of the files files_names = [] for file in list_files: @@ -418,18 +426,16 @@ def filter_data(cloud_account_values, region_account_values, assessment_value): # Filter REGION # Check if filtered data contains an aws account - # TODO - Handle azure locations - if "LOCATION" in filtered_data.columns: - filtered_data.rename(columns={"LOCATION": "REGION"}, inplace=True) if "REGION" not in filtered_data.columns: filtered_data["REGION"] = "-" + if "LOCATION" in filtered_data.columns: + filtered_data.rename(columns={"LOCATION": "REGION"}, inplace=True) if region_account_values == ["All"]: updated_region_account_values = filtered_data["REGION"].unique() elif "All" in region_account_values and len(region_account_values) > 1: # Remove 'All' from the list region_account_values.remove("All") updated_region_account_values = region_account_values - elif len(region_account_values) == 0: updated_region_account_values = filtered_data["REGION"].unique() region_account_values = ["All"] @@ -574,9 +580,11 @@ def filter_data(cloud_account_values, region_account_values, assessment_value): "PASS": "#54d283", "INFO": "#2684FF", "MANUAL": "#636c78", + "WARNING": "#fca903", "MUTED (FAIL)": "#fca903", "MUTED (PASS)": "#03fccf", "MUTED (MANUAL)": "#b33696", + "MUTED (WARNING)": "#c7a45d", } # Define custom colors color_mapping = { @@ -617,13 +625,8 @@ def filter_data(cloud_account_values, region_account_values, assessment_value): style={"height": "300px", "overflow-y": "auto"}, ) - # Figure for the bar chart - color_bars = [ - color_mapping["critical"], - color_mapping["high"], - color_mapping["medium"], - color_mapping["low"], + color_mapping[severity] for severity in df1["SEVERITY"].value_counts().index ] figure_bars = go.Figure( @@ -651,11 +654,17 @@ def filter_data(cloud_account_values, region_account_values, assessment_value): ) # TABLE - severity_dict = {"critical": 3, "high": 2, "medium": 1, "low": 0} + severity_dict = { + "critical": 4, + "high": 3, + "medium": 2, + "low": 1, + "informational": 0, + } fails_findings["SEVERITY"] = fails_findings["SEVERITY"].map(severity_dict) fails_findings = fails_findings.sort_values(by=["SEVERITY"], ascending=False) fails_findings["SEVERITY"] = fails_findings["SEVERITY"].replace( - {3: "critical", 2: "high", 1: "medium", 0: "low"} + {4: "critical", 3: "high", 2: "medium", 1: "low", 0: "informational"} ) table_data = fails_findings.copy()