main
py 193 lines 7.86 KB
Raw
1 from typing import List
2 from typing import Optional
3
4 from fastapi import APIRouter
5 from fastapi import Depends
6 from fastapi import HTTPException
7 from fastapi import Query
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.models.users import User
14 from app.auth.utils import AuthHandler
15 from app.db.db_session import get_db
16 from app.db.universal_models import Customers
17 from app.middleware.customer_access import customer_access_handler
18 from app.siem.schema.dashboards import DashboardCategoriesListResponse
19 from app.siem.schema.dashboards import DashboardCategoryDetailResponse
20 from app.siem.schema.dashboards import DisableDashboardResponse
21 from app.siem.schema.dashboards import EnableDashboardRequest
22 from app.siem.schema.dashboards import EnabledDashboardOperationResponse
23 from app.siem.schema.dashboards import EnabledDashboardResponse
24 from app.siem.schema.dashboards import EnabledDashboardsListResponse
25 from app.siem.schema.dashboards import PanelDataRequest
26 from app.siem.schema.dashboards import PanelDataResponse
27 from app.siem.services.dashboards import disable_dashboard
28 from app.siem.services.dashboards import enable_dashboard
29 from app.siem.services.dashboards import get_category_detail
30 from app.siem.services.dashboards import get_enabled_dashboards
31 from app.siem.services.dashboards import get_enabled_dashboards_for_customers
32 from app.siem.services.dashboards import get_panel_data
33 from app.siem.services.dashboards import list_categories
34
35 dashboards_router = APIRouter()
36
37
38 async def verify_customer_exists(customer_code: str, db: AsyncSession) -> None:
39 result = await db.execute(
40 select(Customers).filter(Customers.customer_code == customer_code),
41 )
42 if not result.scalars().first():
43 raise HTTPException(
44 status_code=404,
45 detail=f"Customer with customer_code {customer_code} not found",
46 )
47
48
49 # ── Browse available templates (filesystem) ─────────────────────
50
51
52 @dashboards_router.get(
53 "/templates",
54 response_model=DashboardCategoriesListResponse,
55 description="List all available dashboard categories (e.g. wazuh_edr, fortinet_edr)",
56 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
57 )
58 async def list_dashboard_categories() -> DashboardCategoriesListResponse:
59 logger.info("Listing dashboard categories")
60 categories = list_categories()
61 return DashboardCategoriesListResponse(
62 categories=categories,
63 success=True,
64 message="Dashboard categories retrieved successfully",
65 )
66
67
68 @dashboards_router.get(
69 "/templates/{category_id}",
70 response_model=DashboardCategoryDetailResponse,
71 description="Get a dashboard category with all its template definitions",
72 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
73 )
74 async def get_dashboard_category(category_id: str) -> DashboardCategoryDetailResponse:
75 logger.info(f"Getting dashboard category {category_id}")
76 category = get_category_detail(category_id)
77 return DashboardCategoryDetailResponse(
78 category=category,
79 success=True,
80 message="Dashboard category retrieved successfully",
81 )
82
83
84 # ── Enabled dashboards (per-customer, DB-backed) ────────────────
85
86
87 @dashboards_router.get(
88 "/enabled",
89 response_model=EnabledDashboardsListResponse,
90 description="List enabled dashboards across the user's accessible customers (optionally narrowed to a subset via customer_codes).",
91 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
92 )
93 async def list_enabled_dashboards_multi(
94 customer_codes: Optional[List[str]] = Query(None, description="Optional subset of customer codes to scope the results to"),
95 current_user: User = Depends(AuthHandler().get_current_user),
96 db: AsyncSession = Depends(get_db),
97 ) -> EnabledDashboardsListResponse:
98 # Resolve the requested subset against the user's access (never widens scope).
99 effective_customers = await customer_access_handler.resolve_effective_customers(current_user, customer_codes, db)
100 rows = await get_enabled_dashboards_for_customers(effective_customers, db)
101 return EnabledDashboardsListResponse(
102 enabled_dashboards=[EnabledDashboardResponse.from_orm(r) for r in rows],
103 success=True,
104 message="Enabled dashboards retrieved successfully",
105 )
106
107
108 @dashboards_router.get(
109 "/enabled/{customer_code}",
110 response_model=EnabledDashboardsListResponse,
111 description="List dashboards enabled for a customer",
112 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
113 )
114 async def list_enabled_dashboards(
115 customer_code: str,
116 current_user: User = Depends(AuthHandler().get_current_user),
117 db: AsyncSession = Depends(get_db),
118 ) -> EnabledDashboardsListResponse:
119 logger.info(f"Listing enabled dashboards for customer {customer_code}")
120 if not await customer_access_handler.check_customer_access(current_user, customer_code, db):
121 raise HTTPException(status_code=403, detail=f"Access denied to customer {customer_code}")
122 await verify_customer_exists(customer_code, db)
123 rows = await get_enabled_dashboards(customer_code, db)
124 return EnabledDashboardsListResponse(
125 enabled_dashboards=[EnabledDashboardResponse.from_orm(r) for r in rows],
126 success=True,
127 message="Enabled dashboards retrieved successfully",
128 )
129
130
131 @dashboards_router.post(
132 "/enable",
133 response_model=EnabledDashboardOperationResponse,
134 description="Enable a dashboard template for a customer + event source",
135 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
136 )
137 async def enable_dashboard_endpoint(
138 request: EnableDashboardRequest,
139 db: AsyncSession = Depends(get_db),
140 ) -> EnabledDashboardOperationResponse:
141 logger.info(f"Enabling dashboard for customer {request.customer_code}")
142 await verify_customer_exists(request.customer_code, db)
143 row = await enable_dashboard(request, db)
144 return EnabledDashboardOperationResponse(
145 enabled_dashboard=EnabledDashboardResponse.from_orm(row),
146 success=True,
147 message="Dashboard enabled successfully",
148 )
149
150
151 @dashboards_router.delete(
152 "/disable/{dashboard_id}",
153 response_model=DisableDashboardResponse,
154 description="Disable (remove) an enabled dashboard",
155 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
156 )
157 async def disable_dashboard_endpoint(
158 dashboard_id: int,
159 db: AsyncSession = Depends(get_db),
160 ) -> DisableDashboardResponse:
161 logger.info(f"Disabling dashboard {dashboard_id}")
162 await disable_dashboard(dashboard_id, db)
163 return DisableDashboardResponse(
164 success=True,
165 message="Dashboard disabled successfully",
166 )
167
168
169 # ── Panel data (execute queries and return chart-ready data) ─────
170
171
172 @dashboards_router.post(
173 "/panel-data",
174 response_model=PanelDataResponse,
175 description="Execute all panel queries for an enabled dashboard and return chart-ready data",
176 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
177 )
178 async def panel_data_endpoint(
179 request: PanelDataRequest,
180 db: AsyncSession = Depends(get_db),
181 ) -> PanelDataResponse:
182 logger.info(f"Fetching panel data for dashboard {request.dashboard_id} (timerange={request.timerange})")
183 data = await get_panel_data(request.dashboard_id, request.timerange, db)
184 return PanelDataResponse(
185 panels=data["results"],
186 template=data["template"],
187 dashboard_id=request.dashboard_id,
188 customer_code=data["customer_code"],
189 source_name=data["source_name"],
190 accent_color=data["accent_color"],
191 success=True,
192 message="Panel data retrieved successfully",
193 )