truthound-dashboard 1.4.4__py3-none-any.whl → 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- truthound_dashboard/api/alerts.py +75 -86
- truthound_dashboard/api/anomaly.py +7 -13
- truthound_dashboard/api/cross_alerts.py +38 -52
- truthound_dashboard/api/drift.py +49 -59
- truthound_dashboard/api/drift_monitor.py +234 -79
- truthound_dashboard/api/enterprise_sampling.py +498 -0
- truthound_dashboard/api/history.py +57 -5
- truthound_dashboard/api/lineage.py +3 -48
- truthound_dashboard/api/maintenance.py +104 -49
- truthound_dashboard/api/mask.py +1 -2
- truthound_dashboard/api/middleware.py +2 -1
- truthound_dashboard/api/model_monitoring.py +435 -311
- truthound_dashboard/api/notifications.py +227 -191
- truthound_dashboard/api/notifications_advanced.py +21 -20
- truthound_dashboard/api/observability.py +586 -0
- truthound_dashboard/api/plugins.py +2 -433
- truthound_dashboard/api/profile.py +199 -37
- truthound_dashboard/api/quality_reporter.py +701 -0
- truthound_dashboard/api/reports.py +7 -16
- truthound_dashboard/api/router.py +66 -0
- truthound_dashboard/api/rule_suggestions.py +5 -5
- truthound_dashboard/api/scan.py +17 -19
- truthound_dashboard/api/schedules.py +85 -50
- truthound_dashboard/api/schema_evolution.py +6 -6
- truthound_dashboard/api/schema_watcher.py +667 -0
- truthound_dashboard/api/sources.py +98 -27
- truthound_dashboard/api/tiering.py +1323 -0
- truthound_dashboard/api/triggers.py +14 -11
- truthound_dashboard/api/validations.py +12 -11
- truthound_dashboard/api/versioning.py +1 -6
- truthound_dashboard/core/__init__.py +129 -3
- truthound_dashboard/core/actions/__init__.py +62 -0
- truthound_dashboard/core/actions/custom.py +426 -0
- truthound_dashboard/core/actions/notifications.py +910 -0
- truthound_dashboard/core/actions/storage.py +472 -0
- truthound_dashboard/core/actions/webhook.py +281 -0
- truthound_dashboard/core/anomaly.py +262 -67
- truthound_dashboard/core/anomaly_explainer.py +4 -3
- truthound_dashboard/core/backends/__init__.py +67 -0
- truthound_dashboard/core/backends/base.py +299 -0
- truthound_dashboard/core/backends/errors.py +191 -0
- truthound_dashboard/core/backends/factory.py +423 -0
- truthound_dashboard/core/backends/mock_backend.py +451 -0
- truthound_dashboard/core/backends/truthound_backend.py +718 -0
- truthound_dashboard/core/checkpoint/__init__.py +87 -0
- truthound_dashboard/core/checkpoint/adapters.py +814 -0
- truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
- truthound_dashboard/core/checkpoint/runner.py +270 -0
- truthound_dashboard/core/connections.py +437 -10
- truthound_dashboard/core/converters/__init__.py +14 -0
- truthound_dashboard/core/converters/truthound.py +620 -0
- truthound_dashboard/core/cross_alerts.py +540 -320
- truthound_dashboard/core/datasource_factory.py +1672 -0
- truthound_dashboard/core/drift_monitor.py +216 -20
- truthound_dashboard/core/enterprise_sampling.py +1291 -0
- truthound_dashboard/core/interfaces/__init__.py +225 -0
- truthound_dashboard/core/interfaces/actions.py +652 -0
- truthound_dashboard/core/interfaces/base.py +247 -0
- truthound_dashboard/core/interfaces/checkpoint.py +676 -0
- truthound_dashboard/core/interfaces/protocols.py +664 -0
- truthound_dashboard/core/interfaces/reporters.py +650 -0
- truthound_dashboard/core/interfaces/routing.py +646 -0
- truthound_dashboard/core/interfaces/triggers.py +619 -0
- truthound_dashboard/core/lineage.py +407 -71
- truthound_dashboard/core/model_monitoring.py +431 -3
- truthound_dashboard/core/notifications/base.py +4 -0
- truthound_dashboard/core/notifications/channels.py +501 -1203
- truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
- truthound_dashboard/core/notifications/deduplication/service.py +131 -348
- truthound_dashboard/core/notifications/dispatcher.py +202 -11
- truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
- truthound_dashboard/core/notifications/escalation/engine.py +168 -358
- truthound_dashboard/core/notifications/routing/__init__.py +88 -128
- truthound_dashboard/core/notifications/routing/engine.py +90 -317
- truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
- truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
- truthound_dashboard/core/notifications/throttling/builder.py +117 -255
- truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
- truthound_dashboard/core/phase5/collaboration.py +1 -1
- truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
- truthound_dashboard/core/quality_reporter.py +1359 -0
- truthound_dashboard/core/report_history.py +0 -6
- truthound_dashboard/core/reporters/__init__.py +175 -14
- truthound_dashboard/core/reporters/adapters.py +943 -0
- truthound_dashboard/core/reporters/base.py +0 -3
- truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
- truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
- truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
- truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
- truthound_dashboard/core/reporters/compat.py +266 -0
- truthound_dashboard/core/reporters/csv_reporter.py +2 -35
- truthound_dashboard/core/reporters/factory.py +526 -0
- truthound_dashboard/core/reporters/interfaces.py +745 -0
- truthound_dashboard/core/reporters/registry.py +1 -10
- truthound_dashboard/core/scheduler.py +165 -0
- truthound_dashboard/core/schema_evolution.py +3 -3
- truthound_dashboard/core/schema_watcher.py +1528 -0
- truthound_dashboard/core/services.py +595 -76
- truthound_dashboard/core/store_manager.py +810 -0
- truthound_dashboard/core/streaming_anomaly.py +169 -4
- truthound_dashboard/core/tiering.py +1309 -0
- truthound_dashboard/core/triggers/evaluators.py +178 -8
- truthound_dashboard/core/truthound_adapter.py +2620 -197
- truthound_dashboard/core/unified_alerts.py +23 -20
- truthound_dashboard/db/__init__.py +8 -0
- truthound_dashboard/db/database.py +8 -2
- truthound_dashboard/db/models.py +944 -25
- truthound_dashboard/db/repository.py +2 -0
- truthound_dashboard/main.py +11 -0
- truthound_dashboard/schemas/__init__.py +177 -16
- truthound_dashboard/schemas/base.py +44 -23
- truthound_dashboard/schemas/collaboration.py +19 -6
- truthound_dashboard/schemas/cross_alerts.py +19 -3
- truthound_dashboard/schemas/drift.py +61 -55
- truthound_dashboard/schemas/drift_monitor.py +67 -23
- truthound_dashboard/schemas/enterprise_sampling.py +653 -0
- truthound_dashboard/schemas/lineage.py +0 -33
- truthound_dashboard/schemas/mask.py +10 -8
- truthound_dashboard/schemas/model_monitoring.py +89 -10
- truthound_dashboard/schemas/notifications_advanced.py +13 -0
- truthound_dashboard/schemas/observability.py +453 -0
- truthound_dashboard/schemas/plugins.py +0 -280
- truthound_dashboard/schemas/profile.py +154 -247
- truthound_dashboard/schemas/quality_reporter.py +403 -0
- truthound_dashboard/schemas/reports.py +2 -2
- truthound_dashboard/schemas/rule_suggestion.py +8 -1
- truthound_dashboard/schemas/scan.py +4 -24
- truthound_dashboard/schemas/schedule.py +11 -3
- truthound_dashboard/schemas/schema_watcher.py +727 -0
- truthound_dashboard/schemas/source.py +17 -2
- truthound_dashboard/schemas/tiering.py +822 -0
- truthound_dashboard/schemas/triggers.py +16 -0
- truthound_dashboard/schemas/unified_alerts.py +7 -0
- truthound_dashboard/schemas/validation.py +0 -13
- truthound_dashboard/schemas/validators/base.py +41 -21
- truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
- truthound_dashboard/schemas/validators/localization_validators.py +273 -0
- truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
- truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
- truthound_dashboard/schemas/validators/referential_validators.py +312 -0
- truthound_dashboard/schemas/validators/registry.py +93 -8
- truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
- truthound_dashboard/schemas/versioning.py +1 -6
- truthound_dashboard/static/index.html +2 -2
- truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
- truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
- truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
- truthound_dashboard/core/plugins/hooks/manager.py +0 -403
- truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
- truthound_dashboard/core/reporters/junit_reporter.py +0 -233
- truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
- truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
- truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
- truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
- truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
- truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
- truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
- truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
- truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
- truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
- truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
- truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
- truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
- truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
- truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
- truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
- truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
- truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
- truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
- truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
- truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
- truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
- truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
- truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
- truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
- truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
- truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
- truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
- truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
- truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
- truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
- truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
- truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
- truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
- truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
- truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
- truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
- truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
- truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
- truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
- truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
- truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
- truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
- truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
- truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
- truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
- truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
- truthound_dashboard-1.4.4.dist-info/METADATA +0 -507
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,12 +13,12 @@ from fastapi import APIRouter, Depends, HTTPException, Query
|
|
|
13
13
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
14
14
|
|
|
15
15
|
from ..core.unified_alerts import UnifiedAlertsService
|
|
16
|
-
from ..db import
|
|
17
|
-
from ..schemas.base import DataResponse
|
|
16
|
+
from ..db import get_db_session
|
|
18
17
|
from ..schemas.unified_alerts import (
|
|
19
18
|
AcknowledgeAlertRequest,
|
|
20
19
|
AlertCorrelation,
|
|
21
20
|
AlertCorrelationResponse,
|
|
21
|
+
AlertCountResponse,
|
|
22
22
|
AlertSeverity,
|
|
23
23
|
AlertSource,
|
|
24
24
|
AlertStatus,
|
|
@@ -33,7 +33,7 @@ from ..schemas.unified_alerts import (
|
|
|
33
33
|
router = APIRouter(prefix="/alerts", tags=["alerts"])
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def get_service(session: AsyncSession = Depends(
|
|
36
|
+
def get_service(session: AsyncSession = Depends(get_db_session)) -> UnifiedAlertsService:
|
|
37
37
|
"""Get unified alerts service instance."""
|
|
38
38
|
return UnifiedAlertsService(session)
|
|
39
39
|
|
|
@@ -43,7 +43,7 @@ def get_service(session: AsyncSession = Depends(get_session)) -> UnifiedAlertsSe
|
|
|
43
43
|
# =============================================================================
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
@router.get("", response_model=
|
|
46
|
+
@router.get("", response_model=UnifiedAlertListResponse)
|
|
47
47
|
async def list_alerts(
|
|
48
48
|
source: AlertSource | None = None,
|
|
49
49
|
severity: AlertSeverity | None = None,
|
|
@@ -53,7 +53,7 @@ async def list_alerts(
|
|
|
53
53
|
offset: int = Query(0, ge=0),
|
|
54
54
|
limit: int = Query(50, ge=1, le=200),
|
|
55
55
|
service: UnifiedAlertsService = Depends(get_service),
|
|
56
|
-
):
|
|
56
|
+
) -> UnifiedAlertListResponse:
|
|
57
57
|
"""List all unified alerts from all sources.
|
|
58
58
|
|
|
59
59
|
Aggregates alerts from:
|
|
@@ -72,34 +72,31 @@ async def list_alerts(
|
|
|
72
72
|
limit=limit,
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
limit=limit,
|
|
81
|
-
)
|
|
75
|
+
return UnifiedAlertListResponse(
|
|
76
|
+
items=alerts,
|
|
77
|
+
total=total,
|
|
78
|
+
offset=offset,
|
|
79
|
+
limit=limit,
|
|
82
80
|
)
|
|
83
81
|
|
|
84
82
|
|
|
85
|
-
@router.get("/summary", response_model=
|
|
83
|
+
@router.get("/summary", response_model=AlertSummary)
|
|
86
84
|
async def get_alert_summary(
|
|
87
85
|
time_range_hours: int = Query(24, ge=1, le=720, description="Time range for summary"),
|
|
88
86
|
service: UnifiedAlertsService = Depends(get_service),
|
|
89
|
-
):
|
|
87
|
+
) -> AlertSummary:
|
|
90
88
|
"""Get alert summary statistics.
|
|
91
89
|
|
|
92
90
|
Returns counts by severity, source, status, and trend data.
|
|
93
91
|
"""
|
|
94
|
-
|
|
95
|
-
return DataResponse(data=summary)
|
|
92
|
+
return await service.get_alert_summary(time_range_hours=time_range_hours)
|
|
96
93
|
|
|
97
94
|
|
|
98
|
-
@router.get("/count")
|
|
95
|
+
@router.get("/count", response_model=AlertCountResponse)
|
|
99
96
|
async def get_alert_count(
|
|
100
97
|
status: AlertStatus | None = Query(None, description="Filter by status"),
|
|
101
98
|
service: UnifiedAlertsService = Depends(get_service),
|
|
102
|
-
):
|
|
99
|
+
) -> AlertCountResponse:
|
|
103
100
|
"""Get quick alert count (for badges).
|
|
104
101
|
|
|
105
102
|
Returns just the count of alerts matching the criteria.
|
|
@@ -110,25 +107,66 @@ async def get_alert_count(
|
|
|
110
107
|
limit=1, # We only need the count
|
|
111
108
|
)
|
|
112
109
|
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"status_filter": status.value if status else "all",
|
|
117
|
-
}
|
|
110
|
+
return AlertCountResponse(
|
|
111
|
+
count=total,
|
|
112
|
+
status_filter=status.value if status else "all",
|
|
118
113
|
)
|
|
119
114
|
|
|
120
115
|
|
|
121
|
-
@router.get("/{alert_id}", response_model=
|
|
116
|
+
@router.get("/{alert_id}", response_model=UnifiedAlertResponse)
|
|
122
117
|
async def get_alert(
|
|
123
118
|
alert_id: str,
|
|
124
119
|
service: UnifiedAlertsService = Depends(get_service),
|
|
125
|
-
):
|
|
120
|
+
) -> UnifiedAlertResponse:
|
|
126
121
|
"""Get a specific alert by unified ID."""
|
|
127
122
|
alert = await service.get_alert_by_id(alert_id)
|
|
128
123
|
if not alert:
|
|
129
124
|
raise HTTPException(status_code=404, detail="Alert not found")
|
|
130
125
|
|
|
131
|
-
return
|
|
126
|
+
return alert
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# =============================================================================
|
|
130
|
+
# Bulk Action Endpoints (must be before /{alert_id} routes to avoid path conflict)
|
|
131
|
+
# =============================================================================
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@router.post("/bulk/acknowledge", response_model=BulkAlertActionResponse)
|
|
135
|
+
async def bulk_acknowledge_alerts(
|
|
136
|
+
request: BulkAlertActionRequest,
|
|
137
|
+
service: UnifiedAlertsService = Depends(get_service),
|
|
138
|
+
) -> BulkAlertActionResponse:
|
|
139
|
+
"""Bulk acknowledge multiple alerts."""
|
|
140
|
+
success, failed, failed_ids = await service.bulk_acknowledge(
|
|
141
|
+
request.alert_ids,
|
|
142
|
+
request.actor,
|
|
143
|
+
request.message,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return BulkAlertActionResponse(
|
|
147
|
+
success_count=success,
|
|
148
|
+
failed_count=failed,
|
|
149
|
+
failed_ids=failed_ids,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@router.post("/bulk/resolve", response_model=BulkAlertActionResponse)
|
|
154
|
+
async def bulk_resolve_alerts(
|
|
155
|
+
request: BulkAlertActionRequest,
|
|
156
|
+
service: UnifiedAlertsService = Depends(get_service),
|
|
157
|
+
) -> BulkAlertActionResponse:
|
|
158
|
+
"""Bulk resolve multiple alerts."""
|
|
159
|
+
success, failed, failed_ids = await service.bulk_resolve(
|
|
160
|
+
request.alert_ids,
|
|
161
|
+
request.actor,
|
|
162
|
+
request.message,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return BulkAlertActionResponse(
|
|
166
|
+
success_count=success,
|
|
167
|
+
failed_count=failed,
|
|
168
|
+
failed_ids=failed_ids,
|
|
169
|
+
)
|
|
132
170
|
|
|
133
171
|
|
|
134
172
|
# =============================================================================
|
|
@@ -136,12 +174,12 @@ async def get_alert(
|
|
|
136
174
|
# =============================================================================
|
|
137
175
|
|
|
138
176
|
|
|
139
|
-
@router.post("/{alert_id}/acknowledge", response_model=
|
|
177
|
+
@router.post("/{alert_id}/acknowledge", response_model=UnifiedAlertResponse)
|
|
140
178
|
async def acknowledge_alert(
|
|
141
179
|
alert_id: str,
|
|
142
180
|
request: AcknowledgeAlertRequest,
|
|
143
181
|
service: UnifiedAlertsService = Depends(get_service),
|
|
144
|
-
):
|
|
182
|
+
) -> UnifiedAlertResponse:
|
|
145
183
|
"""Acknowledge an alert.
|
|
146
184
|
|
|
147
185
|
Note: Not all alert types support acknowledgement.
|
|
@@ -154,15 +192,15 @@ async def acknowledge_alert(
|
|
|
154
192
|
detail="Alert not found or not eligible for acknowledgement",
|
|
155
193
|
)
|
|
156
194
|
|
|
157
|
-
return
|
|
195
|
+
return alert
|
|
158
196
|
|
|
159
197
|
|
|
160
|
-
@router.post("/{alert_id}/resolve", response_model=
|
|
198
|
+
@router.post("/{alert_id}/resolve", response_model=UnifiedAlertResponse)
|
|
161
199
|
async def resolve_alert(
|
|
162
200
|
alert_id: str,
|
|
163
201
|
request: ResolveAlertRequest,
|
|
164
202
|
service: UnifiedAlertsService = Depends(get_service),
|
|
165
|
-
):
|
|
203
|
+
) -> UnifiedAlertResponse:
|
|
166
204
|
"""Resolve an alert.
|
|
167
205
|
|
|
168
206
|
Note: Not all alert types support resolution.
|
|
@@ -175,54 +213,7 @@ async def resolve_alert(
|
|
|
175
213
|
detail="Alert not found or not eligible for resolution",
|
|
176
214
|
)
|
|
177
215
|
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
# =============================================================================
|
|
182
|
-
# Bulk Action Endpoints
|
|
183
|
-
# =============================================================================
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@router.post("/bulk/acknowledge", response_model=DataResponse[BulkAlertActionResponse])
|
|
187
|
-
async def bulk_acknowledge_alerts(
|
|
188
|
-
request: BulkAlertActionRequest,
|
|
189
|
-
service: UnifiedAlertsService = Depends(get_service),
|
|
190
|
-
):
|
|
191
|
-
"""Bulk acknowledge multiple alerts."""
|
|
192
|
-
success, failed, failed_ids = await service.bulk_acknowledge(
|
|
193
|
-
request.alert_ids,
|
|
194
|
-
request.actor,
|
|
195
|
-
request.message,
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
return DataResponse(
|
|
199
|
-
data=BulkAlertActionResponse(
|
|
200
|
-
success_count=success,
|
|
201
|
-
failed_count=failed,
|
|
202
|
-
failed_ids=failed_ids,
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
@router.post("/bulk/resolve", response_model=DataResponse[BulkAlertActionResponse])
|
|
208
|
-
async def bulk_resolve_alerts(
|
|
209
|
-
request: BulkAlertActionRequest,
|
|
210
|
-
service: UnifiedAlertsService = Depends(get_service),
|
|
211
|
-
):
|
|
212
|
-
"""Bulk resolve multiple alerts."""
|
|
213
|
-
success, failed, failed_ids = await service.bulk_resolve(
|
|
214
|
-
request.alert_ids,
|
|
215
|
-
request.actor,
|
|
216
|
-
request.message,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
return DataResponse(
|
|
220
|
-
data=BulkAlertActionResponse(
|
|
221
|
-
success_count=success,
|
|
222
|
-
failed_count=failed,
|
|
223
|
-
failed_ids=failed_ids,
|
|
224
|
-
)
|
|
225
|
-
)
|
|
216
|
+
return alert
|
|
226
217
|
|
|
227
218
|
|
|
228
219
|
# =============================================================================
|
|
@@ -230,12 +221,12 @@ async def bulk_resolve_alerts(
|
|
|
230
221
|
# =============================================================================
|
|
231
222
|
|
|
232
223
|
|
|
233
|
-
@router.get("/{alert_id}/correlations", response_model=
|
|
224
|
+
@router.get("/{alert_id}/correlations", response_model=AlertCorrelationResponse)
|
|
234
225
|
async def get_alert_correlations(
|
|
235
226
|
alert_id: str,
|
|
236
227
|
time_window_hours: int = Query(1, ge=1, le=24, description="Correlation time window"),
|
|
237
228
|
service: UnifiedAlertsService = Depends(get_service),
|
|
238
|
-
):
|
|
229
|
+
) -> AlertCorrelationResponse:
|
|
239
230
|
"""Get correlated alerts for a given alert.
|
|
240
231
|
|
|
241
232
|
Finds alerts that are related by:
|
|
@@ -250,9 +241,7 @@ async def get_alert_correlations(
|
|
|
250
241
|
|
|
251
242
|
total_correlated = sum(len(c.related_alerts) for c in correlations)
|
|
252
243
|
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
total_correlated=total_correlated,
|
|
257
|
-
)
|
|
244
|
+
return AlertCorrelationResponse(
|
|
245
|
+
correlations=correlations,
|
|
246
|
+
total_correlated=total_correlated,
|
|
258
247
|
)
|
|
@@ -115,7 +115,7 @@ async def run_anomaly_detection(
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
@router.get(
|
|
118
|
-
"/anomaly/{detection_id}",
|
|
118
|
+
"/anomaly/detection/{detection_id}",
|
|
119
119
|
response_model=AnomalyDetectionResponse,
|
|
120
120
|
summary="Get detection result",
|
|
121
121
|
description="Get a specific anomaly detection result by ID",
|
|
@@ -182,14 +182,14 @@ async def list_detections(
|
|
|
182
182
|
|
|
183
183
|
@router.get(
|
|
184
184
|
"/sources/{source_id}/anomaly/latest",
|
|
185
|
-
response_model=AnomalyDetectionResponse,
|
|
185
|
+
response_model=AnomalyDetectionResponse | None,
|
|
186
186
|
summary="Get latest detection",
|
|
187
187
|
description="Get the latest anomaly detection result for a source",
|
|
188
188
|
)
|
|
189
189
|
async def get_latest_detection(
|
|
190
190
|
service: AnomalyDetectionServiceDep,
|
|
191
191
|
source_id: Annotated[str, Path(description="Source ID")],
|
|
192
|
-
) -> AnomalyDetectionResponse:
|
|
192
|
+
) -> AnomalyDetectionResponse | None:
|
|
193
193
|
"""Get the latest detection for a source.
|
|
194
194
|
|
|
195
195
|
Args:
|
|
@@ -197,17 +197,11 @@ async def get_latest_detection(
|
|
|
197
197
|
source_id: Source ID.
|
|
198
198
|
|
|
199
199
|
Returns:
|
|
200
|
-
Latest detection result.
|
|
201
|
-
|
|
202
|
-
Raises:
|
|
203
|
-
HTTPException: 404 if no detections found.
|
|
200
|
+
Latest detection result, or None if no detections found.
|
|
204
201
|
"""
|
|
205
202
|
detection = await service.get_latest_detection(source_id)
|
|
206
203
|
if detection is None:
|
|
207
|
-
|
|
208
|
-
status_code=404,
|
|
209
|
-
detail="No detections found for this source",
|
|
210
|
-
)
|
|
204
|
+
return None
|
|
211
205
|
return _detection_to_response(detection)
|
|
212
206
|
|
|
213
207
|
|
|
@@ -246,7 +240,7 @@ async def list_algorithms(
|
|
|
246
240
|
|
|
247
241
|
|
|
248
242
|
@router.post(
|
|
249
|
-
"/anomaly/{detection_id}/explain",
|
|
243
|
+
"/anomaly/detection/{detection_id}/explain",
|
|
250
244
|
response_model=ExplainabilityResponse,
|
|
251
245
|
summary="Generate anomaly explanations",
|
|
252
246
|
description="Generate SHAP/LIME explanations for specific anomaly rows",
|
|
@@ -314,7 +308,7 @@ async def explain_anomaly(
|
|
|
314
308
|
|
|
315
309
|
|
|
316
310
|
@router.get(
|
|
317
|
-
"/anomaly/{detection_id}/explanations",
|
|
311
|
+
"/anomaly/detection/{detection_id}/explanations",
|
|
318
312
|
response_model=CachedExplanationsListResponse,
|
|
319
313
|
summary="Get cached explanations",
|
|
320
314
|
description="Get cached SHAP/LIME explanations for a detection",
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides REST API endpoints for cross-feature integration
|
|
4
4
|
between Anomaly Detection and Drift Monitoring alerts.
|
|
5
|
+
|
|
6
|
+
API Design: Direct Response Style
|
|
7
|
+
- Single resources return the resource directly
|
|
8
|
+
- List endpoints return PaginatedResponse with data, total, offset, limit
|
|
9
|
+
- Errors are handled via HTTPException
|
|
10
|
+
- Success is indicated by HTTP status codes (200, 201, 204)
|
|
5
11
|
"""
|
|
6
12
|
|
|
7
13
|
from __future__ import annotations
|
|
8
14
|
|
|
9
|
-
from typing import Annotated
|
|
15
|
+
from typing import Annotated, Any
|
|
10
16
|
|
|
11
17
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
12
18
|
|
|
@@ -14,6 +20,7 @@ from truthound_dashboard.core.cross_alerts import CrossAlertService
|
|
|
14
20
|
from truthound_dashboard.schemas.cross_alerts import (
|
|
15
21
|
CrossAlertCorrelation,
|
|
16
22
|
CrossAlertCorrelationListResponse,
|
|
23
|
+
CorrelationSearchResult,
|
|
17
24
|
AutoTriggerConfig,
|
|
18
25
|
AutoTriggerConfigCreate,
|
|
19
26
|
AutoTriggerConfigUpdate,
|
|
@@ -70,7 +77,6 @@ async def get_correlations(
|
|
|
70
77
|
)
|
|
71
78
|
|
|
72
79
|
return CrossAlertCorrelationListResponse(
|
|
73
|
-
success=True,
|
|
74
80
|
data=[CrossAlertCorrelation(**c) for c in correlations],
|
|
75
81
|
total=total,
|
|
76
82
|
offset=offset,
|
|
@@ -80,7 +86,7 @@ async def get_correlations(
|
|
|
80
86
|
|
|
81
87
|
@router.get(
|
|
82
88
|
"/cross-alerts/correlations/{source_id}",
|
|
83
|
-
response_model=
|
|
89
|
+
response_model=CorrelationSearchResult,
|
|
84
90
|
summary="Find correlations for source",
|
|
85
91
|
description="Find correlated anomaly and drift alerts for a specific source.",
|
|
86
92
|
)
|
|
@@ -89,7 +95,7 @@ async def find_correlations_for_source(
|
|
|
89
95
|
service: CrossAlertServiceDep,
|
|
90
96
|
time_window_hours: int = Query(24, ge=1, le=168, description="Time window in hours"),
|
|
91
97
|
limit: int = Query(50, ge=1, le=100, description="Maximum items to return"),
|
|
92
|
-
) ->
|
|
98
|
+
) -> CorrelationSearchResult:
|
|
93
99
|
"""Find correlations for a specific source.
|
|
94
100
|
|
|
95
101
|
This actively searches for correlations between recent anomaly
|
|
@@ -102,7 +108,7 @@ async def find_correlations_for_source(
|
|
|
102
108
|
limit: Maximum correlations to return.
|
|
103
109
|
|
|
104
110
|
Returns:
|
|
105
|
-
|
|
111
|
+
Search result with found correlations.
|
|
106
112
|
"""
|
|
107
113
|
correlations = await service.correlate_anomaly_drift(
|
|
108
114
|
source_id=source_id,
|
|
@@ -110,11 +116,10 @@ async def find_correlations_for_source(
|
|
|
110
116
|
limit=limit,
|
|
111
117
|
)
|
|
112
118
|
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
119
|
+
return CorrelationSearchResult(
|
|
120
|
+
correlations=[CrossAlertCorrelation(**c) for c in correlations],
|
|
121
|
+
total=len(correlations),
|
|
122
|
+
)
|
|
118
123
|
|
|
119
124
|
|
|
120
125
|
# =============================================================================
|
|
@@ -124,14 +129,14 @@ async def find_correlations_for_source(
|
|
|
124
129
|
|
|
125
130
|
@router.get(
|
|
126
131
|
"/cross-alerts/config",
|
|
127
|
-
response_model=
|
|
132
|
+
response_model=AutoTriggerConfig,
|
|
128
133
|
summary="Get auto-trigger config",
|
|
129
134
|
description="Get auto-trigger configuration (global or source-specific).",
|
|
130
135
|
)
|
|
131
136
|
async def get_config(
|
|
132
137
|
service: CrossAlertServiceDep,
|
|
133
138
|
source_id: str | None = Query(None, description="Source ID for source-specific config"),
|
|
134
|
-
) ->
|
|
139
|
+
) -> AutoTriggerConfig:
|
|
135
140
|
"""Get auto-trigger configuration.
|
|
136
141
|
|
|
137
142
|
Args:
|
|
@@ -139,18 +144,15 @@ async def get_config(
|
|
|
139
144
|
source_id: Optional source ID for source-specific config.
|
|
140
145
|
|
|
141
146
|
Returns:
|
|
142
|
-
Configuration
|
|
147
|
+
Configuration object.
|
|
143
148
|
"""
|
|
144
|
-
config = service.get_config(source_id)
|
|
145
|
-
return
|
|
146
|
-
"success": True,
|
|
147
|
-
"data": config,
|
|
148
|
-
}
|
|
149
|
+
config = await service.get_config(source_id)
|
|
150
|
+
return AutoTriggerConfig(**config)
|
|
149
151
|
|
|
150
152
|
|
|
151
153
|
@router.post(
|
|
152
154
|
"/cross-alerts/config",
|
|
153
|
-
response_model=
|
|
155
|
+
response_model=AutoTriggerConfig,
|
|
154
156
|
status_code=201,
|
|
155
157
|
summary="Create/update auto-trigger config",
|
|
156
158
|
description="Create or update auto-trigger configuration.",
|
|
@@ -158,7 +160,7 @@ async def get_config(
|
|
|
158
160
|
async def update_config(
|
|
159
161
|
request: AutoTriggerConfigCreate,
|
|
160
162
|
service: CrossAlertServiceDep,
|
|
161
|
-
) ->
|
|
163
|
+
) -> AutoTriggerConfig:
|
|
162
164
|
"""Create or update auto-trigger configuration.
|
|
163
165
|
|
|
164
166
|
Args:
|
|
@@ -171,17 +173,14 @@ async def update_config(
|
|
|
171
173
|
update_data = request.model_dump(exclude_unset=True)
|
|
172
174
|
source_id = update_data.pop("source_id", None)
|
|
173
175
|
|
|
174
|
-
config = service.update_config(source_id, **update_data)
|
|
176
|
+
config = await service.update_config(source_id, **update_data)
|
|
175
177
|
|
|
176
|
-
return
|
|
177
|
-
"success": True,
|
|
178
|
-
"data": config,
|
|
179
|
-
}
|
|
178
|
+
return AutoTriggerConfig(**config)
|
|
180
179
|
|
|
181
180
|
|
|
182
181
|
@router.put(
|
|
183
182
|
"/cross-alerts/config",
|
|
184
|
-
response_model=
|
|
183
|
+
response_model=AutoTriggerConfig,
|
|
185
184
|
summary="Update auto-trigger config",
|
|
186
185
|
description="Update existing auto-trigger configuration.",
|
|
187
186
|
)
|
|
@@ -189,7 +188,7 @@ async def patch_config(
|
|
|
189
188
|
request: AutoTriggerConfigUpdate,
|
|
190
189
|
service: CrossAlertServiceDep,
|
|
191
190
|
source_id: str | None = Query(None, description="Source ID for source-specific config"),
|
|
192
|
-
) ->
|
|
191
|
+
) -> AutoTriggerConfig:
|
|
193
192
|
"""Update auto-trigger configuration.
|
|
194
193
|
|
|
195
194
|
Args:
|
|
@@ -201,12 +200,9 @@ async def patch_config(
|
|
|
201
200
|
Updated configuration.
|
|
202
201
|
"""
|
|
203
202
|
update_data = request.model_dump(exclude_unset=True)
|
|
204
|
-
config = service.update_config(source_id, **update_data)
|
|
203
|
+
config = await service.update_config(source_id, **update_data)
|
|
205
204
|
|
|
206
|
-
return
|
|
207
|
-
"success": True,
|
|
208
|
-
"data": config,
|
|
209
|
-
}
|
|
205
|
+
return AutoTriggerConfig(**config)
|
|
210
206
|
|
|
211
207
|
|
|
212
208
|
# =============================================================================
|
|
@@ -244,7 +240,6 @@ async def list_events(
|
|
|
244
240
|
)
|
|
245
241
|
|
|
246
242
|
return AutoTriggerEventListResponse(
|
|
247
|
-
success=True,
|
|
248
243
|
data=[AutoTriggerEvent(**e) for e in events],
|
|
249
244
|
total=total,
|
|
250
245
|
offset=offset,
|
|
@@ -259,14 +254,14 @@ async def list_events(
|
|
|
259
254
|
|
|
260
255
|
@router.post(
|
|
261
256
|
"/cross-alerts/trigger/drift-on-anomaly/{detection_id}",
|
|
262
|
-
response_model=
|
|
257
|
+
response_model=AutoTriggerEvent,
|
|
263
258
|
summary="Trigger drift check on anomaly",
|
|
264
259
|
description="Manually trigger a drift check based on an anomaly detection result.",
|
|
265
260
|
)
|
|
266
261
|
async def trigger_drift_on_anomaly(
|
|
267
262
|
detection_id: str,
|
|
268
263
|
service: CrossAlertServiceDep,
|
|
269
|
-
) ->
|
|
264
|
+
) -> AutoTriggerEvent:
|
|
270
265
|
"""Manually trigger drift check after anomaly detection.
|
|
271
266
|
|
|
272
267
|
Args:
|
|
@@ -284,22 +279,19 @@ async def trigger_drift_on_anomaly(
|
|
|
284
279
|
if not event:
|
|
285
280
|
raise HTTPException(status_code=404, detail="Detection not found")
|
|
286
281
|
|
|
287
|
-
return
|
|
288
|
-
"success": True,
|
|
289
|
-
"data": event,
|
|
290
|
-
}
|
|
282
|
+
return AutoTriggerEvent(**event)
|
|
291
283
|
|
|
292
284
|
|
|
293
285
|
@router.post(
|
|
294
286
|
"/cross-alerts/trigger/anomaly-on-drift/{monitor_id}",
|
|
295
|
-
response_model=
|
|
287
|
+
response_model=AutoTriggerEvent,
|
|
296
288
|
summary="Trigger anomaly check on drift",
|
|
297
289
|
description="Manually trigger an anomaly check based on drift detection.",
|
|
298
290
|
)
|
|
299
291
|
async def trigger_anomaly_on_drift(
|
|
300
292
|
monitor_id: str,
|
|
301
293
|
service: CrossAlertServiceDep,
|
|
302
|
-
) ->
|
|
294
|
+
) -> AutoTriggerEvent:
|
|
303
295
|
"""Manually trigger anomaly check after drift detection.
|
|
304
296
|
|
|
305
297
|
Args:
|
|
@@ -317,10 +309,7 @@ async def trigger_anomaly_on_drift(
|
|
|
317
309
|
if not event:
|
|
318
310
|
raise HTTPException(status_code=404, detail="Monitor not found")
|
|
319
311
|
|
|
320
|
-
return
|
|
321
|
-
"success": True,
|
|
322
|
-
"data": event,
|
|
323
|
-
}
|
|
312
|
+
return AutoTriggerEvent(**event)
|
|
324
313
|
|
|
325
314
|
|
|
326
315
|
# =============================================================================
|
|
@@ -330,23 +319,20 @@ async def trigger_anomaly_on_drift(
|
|
|
330
319
|
|
|
331
320
|
@router.get(
|
|
332
321
|
"/cross-alerts/summary",
|
|
333
|
-
response_model=
|
|
322
|
+
response_model=CrossAlertSummary,
|
|
334
323
|
summary="Get cross-alert summary",
|
|
335
324
|
description="Get summary statistics for cross-alert correlations.",
|
|
336
325
|
)
|
|
337
326
|
async def get_summary(
|
|
338
327
|
service: CrossAlertServiceDep,
|
|
339
|
-
) ->
|
|
328
|
+
) -> CrossAlertSummary:
|
|
340
329
|
"""Get cross-alert summary statistics.
|
|
341
330
|
|
|
342
331
|
Args:
|
|
343
332
|
service: Injected cross-alert service.
|
|
344
333
|
|
|
345
334
|
Returns:
|
|
346
|
-
Summary statistics
|
|
335
|
+
Summary statistics.
|
|
347
336
|
"""
|
|
348
337
|
summary = await service.get_summary()
|
|
349
|
-
return
|
|
350
|
-
"success": True,
|
|
351
|
-
"data": summary,
|
|
352
|
-
}
|
|
338
|
+
return CrossAlertSummary(**summary)
|