diff --git a/dashboard/assets/styles/dist/output.css b/dashboard/assets/styles/dist/output.css index fcde2ec83ea6..0e3141224fa5 100644 --- a/dashboard/assets/styles/dist/output.css +++ b/dashboard/assets/styles/dist/output.css @@ -553,105 +553,6 @@ video { grid-template-columns: minmax(0, 16fr) repeat(11, minmax(0, 11fr)); } -/* Styles for the table in the overview page */ - -.table-overview thead { - display: table; - width: 100%; - table-layout: fixed; -} - -.table-overview tbody { - -ms-overflow-style: none; - /* IE and Edge */ - scrollbar-width: none; - /* Firefox */ -} - -.table-overview tbody tr { - display: table; - width: 100%; - table-layout: fixed; -} - -/* Styles for thead */ - -.table-overview th { - --tw-bg-opacity: 1; - background-color: rgb(41 37 36 / var(--tw-bg-opacity)); - padding-top: 0.75rem; - padding-bottom: 0.75rem; - font-size: 0.875rem; - font-weight: 700; -} - -.table-overview td { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); - padding-top: 0.5rem; - padding-bottom: 0.5rem; - font-size: 0.875rem; - font-weight: 400; - --tw-text-opacity: 1; - color: rgb(41 37 36 / var(--tw-text-opacity)); -} - -/* Check ID */ - -.table-overview td:nth-child(1), - .table-overview th:nth-child(1) { - width: 52%; -} - -/* Severity */ - -.table-overview td:nth-child(2), - .table-overview th:nth-child(2) { - width: 8%; - text-transform: capitalize; -} - -/* Status */ - -.table-overview td:nth-child(3), - .table-overview th:nth-child(3) { - width: 7%; -} - -.table-overview td:nth-child(3) { - font-weight: 700; - --tw-text-opacity: 1; - color: rgb(185 28 28 / var(--tw-text-opacity)); -} - -/* Region */ - -.table-overview td:nth-child(4), - .table-overview th:nth-child(4) { - width: 9%; -} - -/* Service */ - -.table-overview td:nth-child(5), - .table-overview th:nth-child(5) { - width: 6%; -} - -/* Provider */ - -.table-overview td:nth-child(6), - .table-overview th:nth-child(6) { - width: 7%; -} - -/* Account ID */ - -.table-overview td:nth-child(7), - .table-overview th:nth-child(7) { - width: 11%; -} - .visible { visibility: visible; } @@ -1091,6 +992,19 @@ video { /* Firefox */ } +/*Styles for previous-vext-container from table*/ +.previous-next-container { + margin-top: 1rem; + color: #000; +} + +/*Style for input in filter table*/ +.dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner input:not([type=radio]):not([type=checkbox]) { + color: #FFF !important; + opacity: 1 !important; +} + + #_dash-app-content { --tw-bg-opacity: 1; background-color: rgb(231 229 228 / var(--tw-bg-opacity)); diff --git a/dashboard/lib/layouts.py b/dashboard/lib/layouts.py index 17a147f30d92..f8e16aeec38f 100644 --- a/dashboard/lib/layouts.py +++ b/dashboard/lib/layouts.py @@ -5,7 +5,8 @@ def create_layout_overview( account_dropdown: html.Div, date_dropdown: html.Div, region_dropdown: html.Div, - download_button: html.Button, + download_button_csv: html.Button, + download_button_xlsx: html.Button, severity_dropdown: html.Div, service_dropdown: html.Div, table_row_dropdown: html.Div, @@ -95,7 +96,8 @@ def create_layout_overview( ) ), table_row_dropdown, - download_button, + download_button_csv, + download_button_xlsx, ], className="flex justify-between items-center", ), diff --git a/dashboard/pages/overview.py b/dashboard/pages/overview.py index 99e9d75cf08b..5652994b966f 100644 --- a/dashboard/pages/overview.py +++ b/dashboard/pages/overview.py @@ -8,11 +8,10 @@ # Third-party imports import dash -import dash_bootstrap_components as dbc import pandas as pd import plotly.express as px import plotly.graph_objects as go -from dash import callback, ctx, dcc, html +from dash import callback, ctx, dash_table, dcc, html from dash.dependencies import Input, Output # Config import @@ -267,9 +266,15 @@ def load_csv_files(csv_files): service_dropdown = create_service_dropdown(services) # Create the download button - download_button = html.Button( + download_button_csv = html.Button( "Download this table as CSV", - id="download_link", + id="download_link_csv", + n_clicks=0, + className="border-solid border-2 border-prowler-stone-900/10 hover:border-solid hover:border-2 hover:border-prowler-stone-900/10 text-prowler-stone-900 inline-block px-4 py-2 text-xs font-bold uppercase transition-all rounded-lg text-gray-900 hover:bg-prowler-stone-900/10 flex justify-end w-fit", + ) + download_button_xlsx = html.Button( + "Download this table as XLSX", + id="download_link_xlsx", n_clicks=0, className="border-solid border-2 border-prowler-stone-900/10 hover:border-solid hover:border-2 hover:border-prowler-stone-900/10 text-prowler-stone-900 inline-block px-4 py-2 text-xs font-bold uppercase transition-all rounded-lg text-gray-900 hover:bg-prowler-stone-900/10 flex justify-end w-fit", ) @@ -292,7 +297,8 @@ def load_csv_files(csv_files): account_dropdown, date_dropdown, region_dropdown, - download_button, + download_button_csv, + download_button_xlsx, severity_dropdown, service_dropdown, table_row_dropdown, @@ -335,7 +341,8 @@ def load_csv_files(csv_files): Input("cloud-account-filter", "value"), Input("region-filter", "value"), Input("report-date-filter", "value"), - Input("download_link", "n_clicks"), + Input("download_link_csv", "n_clicks"), + Input("download_link_xlsx", "n_clicks"), Input("severity-filter", "value"), Input("service-filter", "value"), Input("table-rows", "value"), @@ -349,7 +356,8 @@ def filter_data( cloud_account_values, region_account_values, assessment_value, - n_clicks, + n_clicks_csv, + n_clicks_xlsx, severity_values, service_values, table_row_values, @@ -360,7 +368,8 @@ def filter_data( k8s_clicks, ): # Use n_clicks for vulture - n_clicks = n_clicks + n_clicks_csv = n_clicks_csv + n_clicks_xlsx = n_clicks_xlsx # Filter the data filtered_data = data.copy() @@ -905,13 +914,13 @@ def filter_data( # Take the values from the table_row_values if table_row_values == -1: - if len(table_data) >= 25: - table_row_values = 25 + if len(table_data) < 25: + table_row_values = len(table_data) else: - table_row_values = "Full" + table_row_values = 25 if len(table_data) < 25: - table_row_values = "Full" + table_row_values = len(table_data) if len(table_data) >= 25: table_row_options.append(25) @@ -921,25 +930,141 @@ def filter_data( table_row_options.append(75) if len(table_data) >= 100: table_row_options.append(100) - table_row_options.append("Full") - - if table_row_values == "Full": - table = dbc.Table.from_dataframe( - table_data, - striped=True, - bordered=False, - hover=True, - className="table-overview", - ) - else: - table_data = table_data[:table_row_values] - table = dbc.Table.from_dataframe( - table_data, - striped=True, - bordered=False, - hover=True, - className="table-overview", - ) + table_row_options.append(len(table_data)) + + table_data["Severity"] = table_data["Severity"].str.capitalize() + + table = dash_table.DataTable( + data=table_data.to_dict("records"), + style_data={"whiteSpace": "normal", "height": "auto", "color": "black"}, + columns=[ + { + "name": "Check ID - Resource UID", + "id": "Check ID", + "deletable": False, + }, + { + "name": "Severity", + "id": "Severity", + "deletable": False, + }, + {"name": "Status", "id": "Status", "deletable": False}, + {"name": "Region", "id": "Region", "deletable": False}, + {"name": "Service", "id": "Service", "deletable": False}, + {"name": "Provider", "id": "Provider", "deletable": False}, + {"name": "Account ID", "id": "Account ID", "deletable": False}, + ], + style_table={"table-layout": "fixed"}, + style_cell={"textAlign": "left", "layout": "fixed"}, + style_header={ + "fontWeight": "bold", + "layout": "fixed", + "backgroundColor": "rgb(41,37,36)", + }, + page_size=table_row_values, + style_data_conditional=[ + { + "if": {"row_index": "odd"}, + "backgroundColor": "rgb(200, 200, 200)", + "width": "100%", + }, + { + "if": { + "filter_query": '{Status} = "FAIL"', # matching rows of a hidden column with the id, `id` + "column_id": "Status", + }, + "backgroundColor": fail_color, + }, + { + "if": { + "filter_query": '{Status} = "PASS"', # matching rows of a hidden column with the id, `id` + "column_id": "Status", + }, + "backgroundColor": pass_color, + }, + { + "if": { + "filter_query": '{Status} = "MANUAL"', # matching rows of a hidden column with the id, `id` + "column_id": "Status", + }, + "backgroundColor": manual_color, + }, + { + "if": { + "filter_query": '{Status} = "INFO"', # matching rows of a hidden column with the id, `id` + "column_id": "Status", + }, + "backgroundColor": info_color, + }, + { + "if": { + "filter_query": '{Status} = "MUTED (FAIL)"', # matching rows of a hidden column with the id, `id` + "column_id": "Status", + }, + "backgroundColor": muted_fail_color, + }, + { + "if": { + "filter_query": '{Status} = "MUTED (PASS)"', # matching rows of a hidden column with the id, `id` + "column_id": "Status", + }, + "backgroundColor": muted_pass_color, + }, + { + "if": { + "filter_query": '{Status} = "MUTED (MANUAL)"', # matching rows of a hidden column with the id, `id` + "column_id": "Status", + }, + "backgroundColor": muted_manual_color, + }, + { + "if": { + "column_id": "Severity", + }, + "text-transform": "capitalize", + }, + ], + style_cell_conditional=[ + {"if": {"column_id": "Check ID + Resource UID"}, "max-width": "58%"}, + { + "if": {"column_id": "Severity"}, + "max-width": "8%", + "text-align": "center", + }, + { + "if": {"column_id": "Status"}, + "max-width": "7%", + "text-align": "center", + }, + { + "if": {"column_id": "Region"}, + "max-width": "9%", + "text-align": "center", + }, + { + "if": {"column_id": "Service"}, + "max-width": "6%", + "text-align": "center", + }, + { + "if": {"column_id": "Provider"}, + "max-width": "7%", + "text-align": "center", + }, + { + "if": {"column_id": "Account ID"}, + "max-width": "11%", + "text-align": "center", + }, + ], + id="table-overview", + sort_action="native", + sort_mode="single", + style_as_list_view=True, + filter_action="native", + filter_options={"placeholder_text": "🔍"}, + style_filter={"background-color": "#3e403f", "color": "white"}, + ) # Status Graphic status_graph = [ @@ -980,7 +1105,7 @@ def filter_data( # Table table_card = [ - html.Div([table], className="grid grid-cols-auto"), + html.Div([table], className="grid grid-cols-auto w-full"), ] # Create Provider Cards @@ -1007,11 +1132,22 @@ def filter_data( ), ) ] - if ctx.triggered_id == "download_link": - - csv_data = dcc.send_data_frame( - table_data.to_csv, "prowler-dashboard-export.csv", index=False - ) + if ( + ctx.triggered_id == "download_link_csv" + or ctx.triggered_id == "download_link_xlsx" + ): + # Cut the data to the wanted rows + table_data = table_data.head(table_row_values) + if ctx.triggered_id == "download_link_csv": + csv_data = dcc.send_data_frame( + table_data.to_csv, "prowler-dashboard-export.csv", index=False + ) + if ctx.triggered_id == "download_link_xlsx": + csv_data = dcc.send_data_frame( + table_data.to_excel, + "prowler-dashboard-export.xlsx", + index=False, + ) return ( status_graph, two_pie_chart, diff --git a/dashboard/src/input.css b/dashboard/src/input.css index 03b14514a081..4dbf6e7795b2 100644 --- a/dashboard/src/input.css +++ b/dashboard/src/input.css @@ -20,73 +20,6 @@ .custom-grid-large { grid-template-columns: minmax(0, 10fr) repeat(11, minmax(0, 11fr)); } - - /* Styles for the table in the overview page */ - .table-overview thead { - display: table; - width: 100%; - table-layout: fixed; - } - - .table-overview tbody { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ - } - - .table-overview tbody tr { - display: table; - width: 100%; - table-layout: fixed; - } - /* Styles for thead */ - .table-overview th { - @apply bg-prowler-stone-900 text-sm py-3 font-bold; - } - - .table-overview td { - @apply text-prowler-stone-900 bg-prowler-white text-sm py-2 font-bold; - } - - /* Check ID */ - .table-overview td:nth-child(1), - .table-overview th:nth-child(1) { - @apply w-[52%]; - } - /* Severity */ - .table-overview td:nth-child(2), - .table-overview th:nth-child(2) { - @apply w-[8%] capitalize; - } - /* Status */ - .table-overview td:nth-child(3), - .table-overview th:nth-child(3) { - @apply w-[7%]; - } - .table-overview td:nth-child(3) { - @apply font-bold text-prowler-error; - } - /* Region */ - .table-overview td:nth-child(4), - .table-overview th:nth-child(4) { - @apply w-[9%]; - } - /* Service */ - .table-overview td:nth-child(5), - .table-overview th:nth-child(5) { - @apply w-[6%]; - } - /* Provider */ - .table-overview td:nth-child(6), - .table-overview th:nth-child(6) { - @apply w-[7%]; - } - /* Account ID */ - .table-overview td:nth-child(7), - .table-overview th:nth-child(7) { - @apply w-[11%]; - } -} - /* Styles for the accordion in the compliance page */ #_dash-app-content .accordion .accordion-header .accordion-button { @apply text-prowler-stone-900 inline-block px-4 text-xs font-bold uppercase transition-all rounded-lg bg-prowler-stone-300 hover:bg-prowler-stone-900/10;