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