mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
220 lines
7.1 KiB
Python
220 lines
7.1 KiB
Python
# Importing Packages
|
|
import sys
|
|
import warnings
|
|
|
|
import click
|
|
import dash
|
|
import dash_bootstrap_components as dbc
|
|
from colorama import Fore, Style
|
|
from dash import dcc, html
|
|
from dash.dependencies import Input, Output
|
|
|
|
from dashboard.config import folder_path_overview
|
|
from prowler.config.config import orange_color
|
|
from prowler.lib.banner import print_banner
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
cli = sys.modules["flask.cli"]
|
|
print_banner()
|
|
print(
|
|
f"{Fore.GREEN}Loading all CSV files from the folder {folder_path_overview} ...\n{Style.RESET_ALL}"
|
|
)
|
|
cli.show_server_banner = lambda *_: click.echo(
|
|
f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are using {Fore.GREEN}{Style.BRIGHT}Prowler Cloud{Style.RESET_ALL} with the S3 integration or that integration \nfrom {Fore.CYAN}{Style.BRIGHT}Prowler CLI{Style.RESET_ALL} and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3://<your-bucket>/output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
|
|
)
|
|
|
|
# Initialize the app - incorporate css
|
|
dashboard = dash.Dash(
|
|
__name__,
|
|
external_stylesheets=[dbc.themes.FLATLY],
|
|
use_pages=True,
|
|
suppress_callback_exceptions=True,
|
|
title="Prowler Dashboard",
|
|
)
|
|
|
|
# ``use_pages`` above already imported dashboard/pages/cloud.py and registered
|
|
# every /cloud/* route. Import its metadata now (after app instantiation) so
|
|
# the sidebar and the gated pages share a single source of truth.
|
|
from dashboard.pages.cloud import CLOUD_FEATURES_BY_SLUG # noqa: E402
|
|
|
|
ICON_DIR = "/assets/images/icons/cloud"
|
|
|
|
# Official marketing "PROWLER / LOCAL DASHBOARD" lockup (white wordmark + teal
|
|
# gradient sublabel) shown in the expanded sidebar. Vector SVG so it stays crisp
|
|
# at any DPI. The sublabel is right-anchored (text-anchor="end"), so a font
|
|
# fallback widens it leftward rather than clipping at the edge.
|
|
prowler_lockup = html.Img(
|
|
src=f"{ICON_DIR}/prowler-lockup.svg",
|
|
alt="Prowler Local Dashboard",
|
|
className="pc-brand-lockup",
|
|
)
|
|
|
|
# Compact brand mark shown only when the sidebar collapses to its icon rail.
|
|
prowler_mark = html.Img(
|
|
src=f"{ICON_DIR}/prowler-mark.svg",
|
|
alt="Prowler",
|
|
className="pc-brand-mark",
|
|
)
|
|
|
|
# Locally functional destinations (Overview + Compliance).
|
|
DASHBOARD_ITEMS = [
|
|
{"label": "Overview", "route": "/", "icon": f"{ICON_DIR}/overview.svg"},
|
|
{
|
|
"label": "Compliance",
|
|
"route": "/compliance",
|
|
"icon": f"{ICON_DIR}/compliance.svg",
|
|
},
|
|
]
|
|
|
|
# Gated navigation groups reference the shared feature metadata by slug so the
|
|
# sidebar and the informational pages never drift apart.
|
|
GATED_GROUPS = [
|
|
("Upgrade to Prowler Cloud", ["lighthouse-ai", "attack-paths", "findings"]),
|
|
("Configuration", ["alerts", "mutelist", "integrations"]),
|
|
("Workspace", ["organization"]),
|
|
]
|
|
|
|
HELP_LINKS = [
|
|
{
|
|
"title": "Help",
|
|
"url": "https://github.com/prowler-cloud/prowler/issues",
|
|
"icon": f"{ICON_DIR}/help.svg",
|
|
},
|
|
{
|
|
"title": "Docs",
|
|
"url": "https://docs.prowler.com",
|
|
"icon": f"{ICON_DIR}/docs.svg",
|
|
},
|
|
]
|
|
|
|
|
|
def _mask_style(icon_url):
|
|
"""Inline style rendering a recolorable mask icon from a local asset."""
|
|
return {
|
|
"WebkitMaskImage": f"url({icon_url})",
|
|
"maskImage": f"url({icon_url})",
|
|
}
|
|
|
|
|
|
def _nav_icon(icon_url):
|
|
return html.Span(className="pc-ico", style=_mask_style(icon_url))
|
|
|
|
|
|
def _nav_item(label, route, icon_url, current_path, gated=False):
|
|
is_active = current_path == route
|
|
class_name = "pc-nav-item pc-active" if is_active else "pc-nav-item"
|
|
|
|
content = [
|
|
_nav_icon(icon_url),
|
|
html.Span(label, className="pc-nav-label"),
|
|
]
|
|
if gated:
|
|
content.append(html.Span("Prowler Cloud", className="pc-pill"))
|
|
|
|
return dcc.Link(content, href=route, className=class_name)
|
|
|
|
|
|
def _section_label(title):
|
|
return html.Div(title, className="pc-section")
|
|
|
|
|
|
def generate_sidebar(current_path):
|
|
children = [
|
|
# Brand lockup: full wordmark when expanded, compact mark when collapsed.
|
|
html.Div(
|
|
[prowler_lockup, prowler_mark],
|
|
className="pc-brand",
|
|
),
|
|
# Dashboards section — the only locally functional destinations.
|
|
_section_label("Dashboards"),
|
|
html.Nav(
|
|
[
|
|
_nav_item(item["label"], item["route"], item["icon"], current_path)
|
|
for item in DASHBOARD_ITEMS
|
|
],
|
|
className="pc-nav",
|
|
),
|
|
]
|
|
|
|
# Gated groups (Prowler Cloud only).
|
|
for section_title, slugs in GATED_GROUPS:
|
|
children.append(_section_label(section_title))
|
|
children.append(
|
|
html.Nav(
|
|
[
|
|
_nav_item(
|
|
CLOUD_FEATURES_BY_SLUG[slug]["nav_label"],
|
|
CLOUD_FEATURES_BY_SLUG[slug]["route"],
|
|
CLOUD_FEATURES_BY_SLUG[slug]["icon"],
|
|
current_path,
|
|
gated=True,
|
|
)
|
|
for slug in slugs
|
|
],
|
|
className="pc-nav",
|
|
)
|
|
)
|
|
|
|
# Help and Docs pinned to the bottom, separated by a neutral top border.
|
|
children.append(
|
|
html.Nav(
|
|
[
|
|
html.A(
|
|
[
|
|
_nav_icon(link["icon"]),
|
|
html.Span(link["title"], className="pc-nav-label"),
|
|
],
|
|
href=link["url"],
|
|
target="_blank",
|
|
rel="noopener noreferrer",
|
|
className="pc-nav-item",
|
|
)
|
|
for link in HELP_LINKS
|
|
],
|
|
className="pc-nav pc-footer",
|
|
)
|
|
)
|
|
|
|
return html.Div(children, className="pc-sidebar pc-font")
|
|
|
|
|
|
# Layout
|
|
dashboard.layout = html.Div(
|
|
[
|
|
html.Link(rel="icon", href="assets/favicon.ico"),
|
|
html.Div(
|
|
[
|
|
# Dynamic sidebar (rebuilt on navigation for active state).
|
|
html.Div(id="navigation-bar"),
|
|
# Main pane hosting the routed page content.
|
|
html.Div(
|
|
html.Div(
|
|
dash.page_container,
|
|
className="pc-main-inner",
|
|
),
|
|
id="content_select",
|
|
className="pc-main pc-font no-scrollbar",
|
|
),
|
|
],
|
|
className="pc-shell",
|
|
),
|
|
],
|
|
className="h-screen mx-auto",
|
|
)
|
|
|
|
|
|
# Callback to update navigation bar.
|
|
#
|
|
# Triggered off Dash Pages' own location (``_pages_location``) rather than a
|
|
# separate ``dcc.Location``. A standalone ``dcc.Location(id="url")`` stops
|
|
# emitting ``pathname`` when navigating between two pages that render an
|
|
# identical component tree — every ``/cloud/*`` gated page shares the same
|
|
# ``build_cloud_layout`` structure — which left the active highlight stuck on
|
|
# the first gated page visited. ``_pages_location`` fires on every route change.
|
|
@dashboard.callback(
|
|
Output("navigation-bar", "children"), [Input("_pages_location", "pathname")]
|
|
)
|
|
def update_nav_bar(pathname):
|
|
return generate_sidebar(pathname)
|