| 1 | import time |
| 2 | from datetime import datetime |
| 3 | from datetime import timedelta |
| 4 | from typing import List |
| 5 | |
| 6 | from fastapi import APIRouter |
| 7 | from fastapi import Depends |
| 8 | from fastapi import Security |
| 9 | from loguru import logger |
| 10 | from sqlalchemy.ext.asyncio import AsyncSession |
| 11 | from sqlalchemy.future import select |
| 12 | |
| 13 | from app.auth.utils import AuthHandler |
| 14 | from app.connectors.grafana.schema.reporting import GenerateReportRequest |
| 15 | from app.connectors.grafana.schema.reporting import GenerateReportResponse |
| 16 | from app.connectors.grafana.schema.reporting import GrafanaDashboardDetailsResponse |
| 17 | from app.connectors.grafana.schema.reporting import GrafanaDashboardPanelsResponse |
| 18 | from app.connectors.grafana.schema.reporting import GrafanaDashboardResponse |
| 19 | from app.connectors.grafana.schema.reporting import GrafanaGenerateIframeLinksRequest |
| 20 | from app.connectors.grafana.schema.reporting import GrafanaGenerateIframeLinksResponse |
| 21 | from app.connectors.grafana.schema.reporting import GrafanaLinksList |
| 22 | from app.connectors.grafana.schema.reporting import GrafanaOrganizationsResponse |
| 23 | from app.connectors.grafana.schema.reporting import Panel |
| 24 | from app.connectors.grafana.schema.reporting import TimeRange |
| 25 | from app.connectors.grafana.services.reporting import generate_report |
| 26 | from app.connectors.grafana.services.reporting import get_dashboard_details |
| 27 | from app.connectors.grafana.services.reporting import get_dashboards |
| 28 | from app.connectors.grafana.services.reporting import get_orgs |
| 29 | from app.connectors.models import Connectors |
| 30 | from app.db.db_session import get_db |
| 31 | from app.middleware.license import is_feature_enabled |
| 32 | |
| 33 | # from app.middleware.license import is_feature_enabled |
| 34 | |
| 35 | # App specific imports |
| 36 | |
| 37 | |
| 38 | grafana_reporting_router = APIRouter() |
| 39 | |
| 40 | |
| 41 | async def get_grafana_url(session: AsyncSession): |
| 42 | connector = await session.execute(select(Connectors).where(Connectors.connector_name == "Grafana")) |
| 43 | connector = connector.scalars().first() |
| 44 | return connector.connector_url |
| 45 | |
| 46 | |
| 47 | def calculate_unix_timestamps(time_range: TimeRange): |
| 48 | now = datetime.now() |
| 49 | if time_range.unit == "m": |
| 50 | start_time = now - timedelta(minutes=time_range.value) |
| 51 | elif time_range.unit == "h": |
| 52 | start_time = now - timedelta(hours=time_range.value) |
| 53 | elif time_range.unit == "d": |
| 54 | start_time = now - timedelta(days=time_range.value) |
| 55 | |
| 56 | timestamp_from = int(time.mktime(start_time.timetuple())) * 1000 |
| 57 | timestamp_to = int(time.mktime(now.timetuple())) * 1000 |
| 58 | |
| 59 | return timestamp_from, timestamp_to |
| 60 | |
| 61 | |
| 62 | def generate_panel_urls(grafana_url: str, request: GrafanaGenerateIframeLinksRequest, timestamp_from: int, timestamp_to: int): |
| 63 | panel_links: List[GrafanaLinksList] = [] |
| 64 | # for panel_id in request.panel_ids: |
| 65 | panel_url = ( |
| 66 | f"{grafana_url}/d-solo/{request.dashboard_uid}/{request.dashboard_title}" |
| 67 | f"?orgId={request.org_id}&from={timestamp_from}&to={timestamp_to}" |
| 68 | f"&panelId={request.panel_id}" |
| 69 | ) |
| 70 | panel_links.append(GrafanaLinksList(panel_id=request.panel_id, panel_url=panel_url)) |
| 71 | return panel_links |
| 72 | |
| 73 | |
| 74 | @grafana_reporting_router.get( |
| 75 | "/orgs", |
| 76 | response_model=GrafanaOrganizationsResponse, |
| 77 | description="Provision Grafana dashboards", |
| 78 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 79 | ) |
| 80 | async def get_grafana_orgs(): |
| 81 | """ |
| 82 | Endpoint to provision Grafana dashboards. |
| 83 | |
| 84 | Args: |
| 85 | request (DashboardProvisionRequest): The request body containing the dashboard provisioning data. |
| 86 | |
| 87 | Returns: |
| 88 | GrafanaDashboardResponse: The response containing the result of the dashboard provisioning. |
| 89 | """ |
| 90 | logger.info("Getting Grafana orgs") |
| 91 | orgs = await get_orgs() |
| 92 | return GrafanaOrganizationsResponse( |
| 93 | message="Organizations collected from Grafana", |
| 94 | orgs=orgs, |
| 95 | success=True, |
| 96 | ) |
| 97 | |
| 98 | |
| 99 | @grafana_reporting_router.get( |
| 100 | "/dashboards/{org_id}", |
| 101 | response_model=GrafanaDashboardResponse, |
| 102 | description="Get Grafana dashboards for reporting", |
| 103 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 104 | ) |
| 105 | async def get_grafana_dashboards(org_id: int): |
| 106 | """ |
| 107 | Endpoint to get Grafana dashboards. |
| 108 | |
| 109 | Args: |
| 110 | org_id (int): The ID of the organization. |
| 111 | |
| 112 | Returns: |
| 113 | GrafanaDashboardResponse: The response containing the result of the dashboard provisioning. |
| 114 | """ |
| 115 | dashboards = await get_dashboards(org_id=org_id) |
| 116 | return GrafanaDashboardResponse( |
| 117 | message="Dashboards collected from Grafana", |
| 118 | dashboards=dashboards, |
| 119 | success=True, |
| 120 | ) |
| 121 | |
| 122 | |
| 123 | @grafana_reporting_router.get( |
| 124 | "/dashboard/{dashboard_uid}", |
| 125 | response_model=GrafanaDashboardDetailsResponse, |
| 126 | description="Get Grafana dashboard", |
| 127 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 128 | ) |
| 129 | async def get_grafana_dashboard_details(dashboard_uid: str): |
| 130 | """ |
| 131 | Endpoint to get Grafana dashboard. |
| 132 | |
| 133 | Args: |
| 134 | dashboard_uid (str): The UID of the dashboard. |
| 135 | |
| 136 | Returns: |
| 137 | GrafanaDashboardResponse: The response containing the result of the dashboard provisioning. |
| 138 | """ |
| 139 | dashboard_details = await get_dashboard_details(dashboard_uid=dashboard_uid) |
| 140 | |
| 141 | return GrafanaDashboardDetailsResponse( |
| 142 | message="Dashboard details collected from Grafana", |
| 143 | dashboard_details=dashboard_details, |
| 144 | success=True, |
| 145 | ) |
| 146 | |
| 147 | |
| 148 | @grafana_reporting_router.get( |
| 149 | "/dashboard_panels/{dashboard_uid}", |
| 150 | response_model=GrafanaDashboardPanelsResponse, |
| 151 | description="Get Grafana dashboard panels", |
| 152 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 153 | ) |
| 154 | async def get_grafana_dashboard_panels(dashboard_uid: str): |
| 155 | """ |
| 156 | Endpoint to get Grafana dashboard panels. |
| 157 | |
| 158 | Args: |
| 159 | dashboard_uid (str): The UID of the dashboard. |
| 160 | |
| 161 | Returns: |
| 162 | GrafanaDashboardResponse: The response containing the result of the dashboard provisioning. |
| 163 | """ |
| 164 | dashboard_details = await get_dashboard_details(dashboard_uid=dashboard_uid) |
| 165 | logger.info(f"Dashboard details: {dashboard_details}") |
| 166 | |
| 167 | # Get the panel id and panel title from the dashboard details for each panel |
| 168 | panels = [] |
| 169 | logger.info("Fetching panels from dashboard details") |
| 170 | for panel in dashboard_details["dashboard"]["panels"]: |
| 171 | if panel["type"] != "row": |
| 172 | panels.append(Panel(id=panel["id"], title=panel["title"])) |
| 173 | logger.info(f"Panels: {panels}") |
| 174 | return GrafanaDashboardPanelsResponse( |
| 175 | message="Panels collected from Grafana", |
| 176 | panels=panels, |
| 177 | success=True, |
| 178 | ) |
| 179 | |
| 180 | |
| 181 | @grafana_reporting_router.post( |
| 182 | "/generate_iframe_links", |
| 183 | response_model=GrafanaGenerateIframeLinksResponse, |
| 184 | description="Generate Grafana dashboard iframe links", |
| 185 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 186 | ) |
| 187 | async def generate_grafana_iframe_links( |
| 188 | request: GrafanaGenerateIframeLinksRequest, |
| 189 | session: AsyncSession = Depends(get_db), |
| 190 | ): |
| 191 | """ |
| 192 | Endpoint to generate Grafana dashboard iframe links. |
| 193 | |
| 194 | Args: |
| 195 | request (GrafanaGenerateIframeLinksRequest): The request body containing the dashboard UID and organization ID. |
| 196 | |
| 197 | Returns: |
| 198 | GrafanaDashboardPanelsResponse: The response containing the result of the dashboard provisioning. |
| 199 | """ |
| 200 | # get the Grafana URL from the database |
| 201 | grafana_url = await get_grafana_url(session) |
| 202 | logger.info(f"Grafana URL: {grafana_url}") |
| 203 | |
| 204 | # calculate the Unix timestamps based on the current time and the provided time range |
| 205 | timestamp_from, timestamp_to = calculate_unix_timestamps(request.time_range) |
| 206 | |
| 207 | # build the URL string for each panel_id |
| 208 | panel_urls = generate_panel_urls(grafana_url, request, timestamp_from, timestamp_to) |
| 209 | |
| 210 | return GrafanaGenerateIframeLinksResponse( |
| 211 | message="Iframe links generated from Grafana", |
| 212 | links=panel_urls, |
| 213 | success=True, |
| 214 | ) |
| 215 | |
| 216 | |
| 217 | @grafana_reporting_router.post( |
| 218 | "/generate-report", |
| 219 | response_model=GenerateReportResponse, |
| 220 | description="Create a new report.", |
| 221 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 222 | ) |
| 223 | async def create_report(request: GenerateReportRequest, session: AsyncSession = Depends(get_db)) -> GenerateReportResponse: |
| 224 | logger.info("Generating report") |
| 225 | await is_feature_enabled("REPORTING", session) |
| 226 | return await generate_report(request, session) |