1package api 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "strconv" 8 9 "github.com/grafana/grafana/pkg/api/dtos" 10 "github.com/grafana/grafana/pkg/api/response" 11 "github.com/grafana/grafana/pkg/bus" 12 "github.com/grafana/grafana/pkg/models" 13 "github.com/grafana/grafana/pkg/services/alerting" 14 "github.com/grafana/grafana/pkg/services/guardian" 15 "github.com/grafana/grafana/pkg/services/ngalert/notifier" 16 "github.com/grafana/grafana/pkg/services/search" 17 "github.com/grafana/grafana/pkg/setting" 18 "github.com/grafana/grafana/pkg/util" 19 "github.com/grafana/grafana/pkg/web" 20) 21 22func ValidateOrgAlert(c *models.ReqContext) { 23 id := c.ParamsInt64(":alertId") 24 query := models.GetAlertByIdQuery{Id: id} 25 26 if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { 27 c.JsonApiErr(404, "Alert not found", nil) 28 return 29 } 30 31 if c.OrgId != query.Result.OrgId { 32 c.JsonApiErr(403, "You are not allowed to edit/view alert", nil) 33 return 34 } 35} 36 37func GetAlertStatesForDashboard(c *models.ReqContext) response.Response { 38 dashboardID := c.QueryInt64("dashboardId") 39 40 if dashboardID == 0 { 41 return response.Error(400, "Missing query parameter dashboardId", nil) 42 } 43 44 query := models.GetAlertStatesForDashboardQuery{ 45 OrgId: c.OrgId, 46 DashboardId: c.QueryInt64("dashboardId"), 47 } 48 49 if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { 50 return response.Error(500, "Failed to fetch alert states", err) 51 } 52 53 return response.JSON(200, query.Result) 54} 55 56// GET /api/alerts 57func GetAlerts(c *models.ReqContext) response.Response { 58 dashboardQuery := c.Query("dashboardQuery") 59 dashboardTags := c.QueryStrings("dashboardTag") 60 stringDashboardIDs := c.QueryStrings("dashboardId") 61 stringFolderIDs := c.QueryStrings("folderId") 62 63 dashboardIDs := make([]int64, 0) 64 for _, id := range stringDashboardIDs { 65 dashboardID, err := strconv.ParseInt(id, 10, 64) 66 if err == nil { 67 dashboardIDs = append(dashboardIDs, dashboardID) 68 } 69 } 70 71 if dashboardQuery != "" || len(dashboardTags) > 0 || len(stringFolderIDs) > 0 { 72 folderIDs := make([]int64, 0) 73 for _, id := range stringFolderIDs { 74 folderID, err := strconv.ParseInt(id, 10, 64) 75 if err == nil { 76 folderIDs = append(folderIDs, folderID) 77 } 78 } 79 80 searchQuery := search.Query{ 81 Title: dashboardQuery, 82 Tags: dashboardTags, 83 SignedInUser: c.SignedInUser, 84 Limit: 1000, 85 OrgId: c.OrgId, 86 DashboardIds: dashboardIDs, 87 Type: string(search.DashHitDB), 88 FolderIds: folderIDs, 89 Permission: models.PERMISSION_VIEW, 90 } 91 92 err := bus.DispatchCtx(c.Req.Context(), &searchQuery) 93 if err != nil { 94 return response.Error(500, "List alerts failed", err) 95 } 96 97 for _, d := range searchQuery.Result { 98 if d.Type == search.DashHitDB && d.ID > 0 { 99 dashboardIDs = append(dashboardIDs, d.ID) 100 } 101 } 102 103 // if we didn't find any dashboards, return empty result 104 if len(dashboardIDs) == 0 { 105 return response.JSON(200, []*models.AlertListItemDTO{}) 106 } 107 } 108 109 query := models.GetAlertsQuery{ 110 OrgId: c.OrgId, 111 DashboardIDs: dashboardIDs, 112 PanelId: c.QueryInt64("panelId"), 113 Limit: c.QueryInt64("limit"), 114 User: c.SignedInUser, 115 Query: c.Query("query"), 116 } 117 118 states := c.QueryStrings("state") 119 if len(states) > 0 { 120 query.State = states 121 } 122 123 if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { 124 return response.Error(500, "List alerts failed", err) 125 } 126 127 for _, alert := range query.Result { 128 alert.Url = models.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug) 129 } 130 131 return response.JSON(200, query.Result) 132} 133 134// POST /api/alerts/test 135func (hs *HTTPServer) AlertTest(c *models.ReqContext, dto dtos.AlertTestCommand) response.Response { 136 if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil { 137 return response.Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil) 138 } 139 140 res, err := hs.AlertEngine.AlertTest(c.OrgId, dto.Dashboard, dto.PanelId, c.SignedInUser) 141 if err != nil { 142 var validationErr alerting.ValidationError 143 if errors.As(err, &validationErr) { 144 return response.Error(422, validationErr.Error(), nil) 145 } 146 if errors.Is(err, models.ErrDataSourceAccessDenied) { 147 return response.Error(403, "Access denied to datasource", err) 148 } 149 return response.Error(500, "Failed to test rule", err) 150 } 151 152 dtoRes := &dtos.AlertTestResult{ 153 Firing: res.Firing, 154 ConditionEvals: res.ConditionEvals, 155 State: res.Rule.State, 156 } 157 158 if res.Error != nil { 159 dtoRes.Error = res.Error.Error() 160 } 161 162 for _, log := range res.Logs { 163 dtoRes.Logs = append(dtoRes.Logs, &dtos.AlertTestResultLog{Message: log.Message, Data: log.Data}) 164 } 165 for _, match := range res.EvalMatches { 166 dtoRes.EvalMatches = append(dtoRes.EvalMatches, &dtos.EvalMatch{Metric: match.Metric, Value: match.Value}) 167 } 168 169 dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs()) 170 171 return response.JSON(200, dtoRes) 172} 173 174// GET /api/alerts/:id 175func GetAlert(c *models.ReqContext) response.Response { 176 id := c.ParamsInt64(":alertId") 177 query := models.GetAlertByIdQuery{Id: id} 178 179 if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { 180 return response.Error(500, "List alerts failed", err) 181 } 182 183 return response.JSON(200, &query.Result) 184} 185 186func GetAlertNotifiers(ngalertEnabled bool) func(*models.ReqContext) response.Response { 187 return func(_ *models.ReqContext) response.Response { 188 if ngalertEnabled { 189 return response.JSON(200, notifier.GetAvailableNotifiers()) 190 } 191 // TODO(codesome): This wont be required in 8.0 since ngalert 192 // will be enabled by default with no disabling. This is to be removed later. 193 return response.JSON(200, alerting.GetNotifiers()) 194 } 195} 196 197func GetAlertNotificationLookup(c *models.ReqContext) response.Response { 198 alertNotifications, err := getAlertNotificationsInternal(c) 199 if err != nil { 200 return response.Error(500, "Failed to get alert notifications", err) 201 } 202 203 result := make([]*dtos.AlertNotificationLookup, 0) 204 205 for _, notification := range alertNotifications { 206 result = append(result, dtos.NewAlertNotificationLookup(notification)) 207 } 208 209 return response.JSON(200, result) 210} 211 212func GetAlertNotifications(c *models.ReqContext) response.Response { 213 alertNotifications, err := getAlertNotificationsInternal(c) 214 if err != nil { 215 return response.Error(500, "Failed to get alert notifications", err) 216 } 217 218 result := make([]*dtos.AlertNotification, 0) 219 220 for _, notification := range alertNotifications { 221 result = append(result, dtos.NewAlertNotification(notification)) 222 } 223 224 return response.JSON(200, result) 225} 226 227func getAlertNotificationsInternal(c *models.ReqContext) ([]*models.AlertNotification, error) { 228 query := &models.GetAllAlertNotificationsQuery{OrgId: c.OrgId} 229 230 if err := bus.DispatchCtx(c.Req.Context(), query); err != nil { 231 return nil, err 232 } 233 234 return query.Result, nil 235} 236 237func GetAlertNotificationByID(c *models.ReqContext) response.Response { 238 query := &models.GetAlertNotificationsQuery{ 239 OrgId: c.OrgId, 240 Id: c.ParamsInt64(":notificationId"), 241 } 242 243 if query.Id == 0 { 244 return response.Error(404, "Alert notification not found", nil) 245 } 246 247 if err := bus.DispatchCtx(c.Req.Context(), query); err != nil { 248 return response.Error(500, "Failed to get alert notifications", err) 249 } 250 251 if query.Result == nil { 252 return response.Error(404, "Alert notification not found", nil) 253 } 254 255 return response.JSON(200, dtos.NewAlertNotification(query.Result)) 256} 257 258func GetAlertNotificationByUID(c *models.ReqContext) response.Response { 259 query := &models.GetAlertNotificationsWithUidQuery{ 260 OrgId: c.OrgId, 261 Uid: web.Params(c.Req)[":uid"], 262 } 263 264 if query.Uid == "" { 265 return response.Error(404, "Alert notification not found", nil) 266 } 267 268 if err := bus.DispatchCtx(c.Req.Context(), query); err != nil { 269 return response.Error(500, "Failed to get alert notifications", err) 270 } 271 272 if query.Result == nil { 273 return response.Error(404, "Alert notification not found", nil) 274 } 275 276 return response.JSON(200, dtos.NewAlertNotification(query.Result)) 277} 278 279func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotificationCommand) response.Response { 280 cmd.OrgId = c.OrgId 281 282 if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { 283 if errors.Is(err, models.ErrAlertNotificationWithSameNameExists) || errors.Is(err, models.ErrAlertNotificationWithSameUIDExists) { 284 return response.Error(409, "Failed to create alert notification", err) 285 } 286 var alertingErr alerting.ValidationError 287 if errors.As(err, &alertingErr) { 288 return response.Error(400, err.Error(), err) 289 } 290 return response.Error(500, "Failed to create alert notification", err) 291 } 292 293 return response.JSON(200, dtos.NewAlertNotification(cmd.Result)) 294} 295 296func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) response.Response { 297 cmd.OrgId = c.OrgId 298 299 err := hs.fillWithSecureSettingsData(c.Req.Context(), &cmd) 300 if err != nil { 301 return response.Error(500, "Failed to update alert notification", err) 302 } 303 304 if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { 305 if errors.Is(err, models.ErrAlertNotificationNotFound) { 306 return response.Error(404, err.Error(), err) 307 } 308 var alertingErr alerting.ValidationError 309 if errors.As(err, &alertingErr) { 310 return response.Error(400, err.Error(), err) 311 } 312 return response.Error(500, "Failed to update alert notification", err) 313 } 314 315 query := models.GetAlertNotificationsQuery{ 316 OrgId: c.OrgId, 317 Id: cmd.Id, 318 } 319 320 if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { 321 return response.Error(500, "Failed to get alert notification", err) 322 } 323 324 return response.JSON(200, dtos.NewAlertNotification(query.Result)) 325} 326 327func (hs *HTTPServer) UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) response.Response { 328 cmd.OrgId = c.OrgId 329 cmd.Uid = web.Params(c.Req)[":uid"] 330 331 err := hs.fillWithSecureSettingsDataByUID(c.Req.Context(), &cmd) 332 if err != nil { 333 return response.Error(500, "Failed to update alert notification", err) 334 } 335 336 if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { 337 if errors.Is(err, models.ErrAlertNotificationNotFound) { 338 return response.Error(404, err.Error(), nil) 339 } 340 return response.Error(500, "Failed to update alert notification", err) 341 } 342 343 query := models.GetAlertNotificationsWithUidQuery{ 344 OrgId: cmd.OrgId, 345 Uid: cmd.Uid, 346 } 347 348 if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { 349 return response.Error(500, "Failed to get alert notification", err) 350 } 351 352 return response.JSON(200, dtos.NewAlertNotification(query.Result)) 353} 354 355func (hs *HTTPServer) fillWithSecureSettingsData(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error { 356 if len(cmd.SecureSettings) == 0 { 357 return nil 358 } 359 360 query := &models.GetAlertNotificationsQuery{ 361 OrgId: cmd.OrgId, 362 Id: cmd.Id, 363 } 364 365 if err := bus.DispatchCtx(ctx, query); err != nil { 366 return err 367 } 368 369 secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey) 370 if err != nil { 371 return err 372 } 373 374 for k, v := range secureSettings { 375 if _, ok := cmd.SecureSettings[k]; !ok { 376 cmd.SecureSettings[k] = v 377 } 378 } 379 380 return nil 381} 382 383func (hs *HTTPServer) fillWithSecureSettingsDataByUID(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error { 384 if len(cmd.SecureSettings) == 0 { 385 return nil 386 } 387 388 query := &models.GetAlertNotificationsWithUidQuery{ 389 OrgId: cmd.OrgId, 390 Uid: cmd.Uid, 391 } 392 393 if err := bus.DispatchCtx(ctx, query); err != nil { 394 return err 395 } 396 397 secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey) 398 if err != nil { 399 return err 400 } 401 402 for k, v := range secureSettings { 403 if _, ok := cmd.SecureSettings[k]; !ok { 404 cmd.SecureSettings[k] = v 405 } 406 } 407 408 return nil 409} 410 411func DeleteAlertNotification(c *models.ReqContext) response.Response { 412 cmd := models.DeleteAlertNotificationCommand{ 413 OrgId: c.OrgId, 414 Id: c.ParamsInt64(":notificationId"), 415 } 416 417 if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { 418 if errors.Is(err, models.ErrAlertNotificationNotFound) { 419 return response.Error(404, err.Error(), nil) 420 } 421 return response.Error(500, "Failed to delete alert notification", err) 422 } 423 424 return response.Success("Notification deleted") 425} 426 427func DeleteAlertNotificationByUID(c *models.ReqContext) response.Response { 428 cmd := models.DeleteAlertNotificationWithUidCommand{ 429 OrgId: c.OrgId, 430 Uid: web.Params(c.Req)[":uid"], 431 } 432 433 if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { 434 if errors.Is(err, models.ErrAlertNotificationNotFound) { 435 return response.Error(404, err.Error(), nil) 436 } 437 return response.Error(500, "Failed to delete alert notification", err) 438 } 439 440 return response.JSON(200, util.DynMap{ 441 "message": "Notification deleted", 442 "id": cmd.DeletedAlertNotificationId, 443 }) 444} 445 446// POST /api/alert-notifications/test 447func NotificationTest(c *models.ReqContext, dto dtos.NotificationTestCommand) response.Response { 448 cmd := &alerting.NotificationTestCommand{ 449 OrgID: c.OrgId, 450 ID: dto.ID, 451 Name: dto.Name, 452 Type: dto.Type, 453 Settings: dto.Settings, 454 SecureSettings: dto.SecureSettings, 455 } 456 457 if err := bus.DispatchCtx(c.Req.Context(), cmd); err != nil { 458 if errors.Is(err, models.ErrSmtpNotEnabled) { 459 return response.Error(412, err.Error(), err) 460 } 461 var alertingErr alerting.ValidationError 462 if errors.As(err, &alertingErr) { 463 return response.Error(400, err.Error(), err) 464 } 465 466 return response.Error(500, "Failed to send alert notifications", err) 467 } 468 469 return response.Success("Test notification sent") 470} 471 472// POST /api/alerts/:alertId/pause 473func PauseAlert(c *models.ReqContext, dto dtos.PauseAlertCommand) response.Response { 474 alertID := c.ParamsInt64(":alertId") 475 result := make(map[string]interface{}) 476 result["alertId"] = alertID 477 478 query := models.GetAlertByIdQuery{Id: alertID} 479 if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { 480 return response.Error(500, "Get Alert failed", err) 481 } 482 483 guardian := guardian.New(c.Req.Context(), query.Result.DashboardId, c.OrgId, c.SignedInUser) 484 if canEdit, err := guardian.CanEdit(); err != nil || !canEdit { 485 if err != nil { 486 return response.Error(500, "Error while checking permissions for Alert", err) 487 } 488 489 return response.Error(403, "Access denied to this dashboard and alert", nil) 490 } 491 492 // Alert state validation 493 if query.Result.State != models.AlertStatePaused && !dto.Paused { 494 result["state"] = "un-paused" 495 result["message"] = "Alert is already un-paused" 496 return response.JSON(200, result) 497 } else if query.Result.State == models.AlertStatePaused && dto.Paused { 498 result["state"] = models.AlertStatePaused 499 result["message"] = "Alert is already paused" 500 return response.JSON(200, result) 501 } 502 503 cmd := models.PauseAlertCommand{ 504 OrgId: c.OrgId, 505 AlertIds: []int64{alertID}, 506 Paused: dto.Paused, 507 } 508 509 if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { 510 return response.Error(500, "", err) 511 } 512 513 var resp models.AlertStateType = models.AlertStateUnknown 514 pausedState := "un-paused" 515 if cmd.Paused { 516 resp = models.AlertStatePaused 517 pausedState = "paused" 518 } 519 520 result["state"] = resp 521 result["message"] = "Alert " + pausedState 522 return response.JSON(200, result) 523} 524 525// POST /api/admin/pause-all-alerts 526func PauseAllAlerts(c *models.ReqContext, dto dtos.PauseAllAlertsCommand) response.Response { 527 updateCmd := models.PauseAllAlertCommand{ 528 Paused: dto.Paused, 529 } 530 531 if err := bus.DispatchCtx(c.Req.Context(), &updateCmd); err != nil { 532 return response.Error(500, "Failed to pause alerts", err) 533 } 534 535 var resp models.AlertStateType = models.AlertStatePending 536 pausedState := "un paused" 537 if updateCmd.Paused { 538 resp = models.AlertStatePaused 539 pausedState = "paused" 540 } 541 542 result := map[string]interface{}{ 543 "state": resp, 544 "message": "alerts " + pausedState, 545 "alertsAffected": updateCmd.ResultCount, 546 } 547 548 return response.JSON(200, result) 549} 550