mirror of
https://github.com/grafana/grafana.git
synced 2025-01-01 11:47:05 -06:00
c18da48e50
* Separate overlapping legacy and UA alerting routes api/alert-notifiers, alerting/list, and alerting/notifications existed in both legacy and UA. Rename legacy route paths and nav ids to be independent of UA ones.
1050 lines
32 KiB
Go
1050 lines
32 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/grafana/grafana/pkg/api/dtos"
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/services/alerting"
|
|
alertmodels "github.com/grafana/grafana/pkg/services/alerting/models"
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
"github.com/grafana/grafana/pkg/services/guardian"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
|
|
"github.com/grafana/grafana/pkg/services/notifications"
|
|
"github.com/grafana/grafana/pkg/services/search"
|
|
"github.com/grafana/grafana/pkg/services/search/model"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
"github.com/grafana/grafana/pkg/web"
|
|
)
|
|
|
|
func (hs *HTTPServer) ValidateOrgAlert(c *contextmodel.ReqContext) {
|
|
id, err := strconv.ParseInt(web.Params(c.Req)[":alertId"], 10, 64)
|
|
if err != nil {
|
|
c.JsonApiErr(http.StatusBadRequest, "alertId is invalid", nil)
|
|
return
|
|
}
|
|
query := alertmodels.GetAlertByIdQuery{ID: id}
|
|
|
|
res, err := hs.AlertEngine.AlertStore.GetAlertById(c.Req.Context(), &query)
|
|
if err != nil {
|
|
c.JsonApiErr(404, "Alert not found", nil)
|
|
return
|
|
}
|
|
|
|
if c.SignedInUser.GetOrgID() != res.OrgID {
|
|
c.JsonApiErr(403, "You are not allowed to edit/view alert", nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
// swagger:route GET /alerts/states-for-dashboard legacy_alerts getDashboardStates
|
|
//
|
|
// Get alert states for a dashboard.
|
|
//
|
|
// Responses:
|
|
// Responses:
|
|
// 200: getDashboardStatesResponse
|
|
// 400: badRequestError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetAlertStatesForDashboard(c *contextmodel.ReqContext) response.Response {
|
|
dashboardID := c.QueryInt64("dashboardId")
|
|
|
|
if dashboardID == 0 {
|
|
return response.Error(400, "Missing query parameter dashboardId", nil)
|
|
}
|
|
|
|
query := alertmodels.GetAlertStatesForDashboardQuery{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
DashboardID: c.QueryInt64("dashboardId"),
|
|
}
|
|
|
|
res, err := hs.AlertEngine.AlertStore.GetAlertStatesForDashboard(c.Req.Context(), &query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to fetch alert states", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// swagger:route GET /alerts legacy_alerts getAlerts
|
|
//
|
|
// Get legacy alerts.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertsResponse
|
|
// 401: unauthorisedError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetAlerts(c *contextmodel.ReqContext) response.Response {
|
|
dashboardQuery := c.Query("dashboardQuery")
|
|
dashboardTags := c.QueryStrings("dashboardTag")
|
|
stringDashboardIDs := c.QueryStrings("dashboardId")
|
|
stringFolderIDs := c.QueryStrings("folderId")
|
|
|
|
dashboardIDs := make([]int64, 0)
|
|
for _, id := range stringDashboardIDs {
|
|
dashboardID, err := strconv.ParseInt(id, 10, 64)
|
|
if err == nil {
|
|
dashboardIDs = append(dashboardIDs, dashboardID)
|
|
}
|
|
}
|
|
|
|
if dashboardQuery != "" || len(dashboardTags) > 0 || len(stringFolderIDs) > 0 {
|
|
folderIDs := make([]int64, 0)
|
|
for _, id := range stringFolderIDs {
|
|
folderID, err := strconv.ParseInt(id, 10, 64)
|
|
if err == nil {
|
|
folderIDs = append(folderIDs, folderID)
|
|
}
|
|
}
|
|
|
|
searchQuery := search.Query{
|
|
Title: dashboardQuery,
|
|
Tags: dashboardTags,
|
|
SignedInUser: c.SignedInUser,
|
|
Limit: 1000,
|
|
OrgId: c.SignedInUser.GetOrgID(),
|
|
DashboardIds: dashboardIDs,
|
|
Type: string(model.DashHitDB),
|
|
FolderIds: folderIDs, // nolint:staticcheck
|
|
Permission: dashboardaccess.PERMISSION_VIEW,
|
|
}
|
|
|
|
hits, err := hs.SearchService.SearchHandler(c.Req.Context(), &searchQuery)
|
|
if err != nil {
|
|
return response.Error(500, "List alerts failed", err)
|
|
}
|
|
|
|
for _, d := range hits {
|
|
if d.Type == model.DashHitDB && d.ID > 0 {
|
|
dashboardIDs = append(dashboardIDs, d.ID)
|
|
}
|
|
}
|
|
|
|
// if we didn't find any dashboards, return empty result
|
|
if len(dashboardIDs) == 0 {
|
|
return response.JSON(http.StatusOK, []*alertmodels.AlertListItemDTO{})
|
|
}
|
|
}
|
|
|
|
query := alertmodels.GetAlertsQuery{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
DashboardIDs: dashboardIDs,
|
|
PanelID: c.QueryInt64("panelId"),
|
|
Limit: c.QueryInt64("limit"),
|
|
User: c.SignedInUser,
|
|
Query: c.Query("query"),
|
|
}
|
|
|
|
states := c.QueryStrings("state")
|
|
if len(states) > 0 {
|
|
query.State = states
|
|
}
|
|
|
|
res, err := hs.AlertEngine.AlertStore.HandleAlertsQuery(c.Req.Context(), &query)
|
|
if err != nil {
|
|
return response.Error(500, "List alerts failed", err)
|
|
}
|
|
|
|
for _, alert := range res {
|
|
alert.URL = dashboards.GetDashboardURL(alert.DashboardUID, alert.DashboardSlug)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// swagger:route POST /alerts/test legacy_alerts testAlert
|
|
//
|
|
// Test alert.
|
|
//
|
|
// Responses:
|
|
// 200: testAlertResponse
|
|
// 400: badRequestError
|
|
// 422: unprocessableEntityError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) AlertTest(c *contextmodel.ReqContext) response.Response {
|
|
dto := dtos.AlertTestCommand{}
|
|
if err := web.Bind(c.Req, &dto); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil {
|
|
return response.Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
|
|
}
|
|
|
|
res, err := hs.AlertEngine.AlertTest(c.SignedInUser.GetOrgID(), dto.Dashboard, dto.PanelId, c.SignedInUser)
|
|
if err != nil {
|
|
var validationErr alerting.ValidationError
|
|
if errors.As(err, &validationErr) {
|
|
return response.Error(422, validationErr.Error(), nil)
|
|
}
|
|
if errors.Is(err, datasources.ErrDataSourceAccessDenied) {
|
|
return response.Error(403, "Access denied to datasource", err)
|
|
}
|
|
return response.Error(500, "Failed to test rule", err)
|
|
}
|
|
|
|
dtoRes := &dtos.AlertTestResult{
|
|
Firing: res.Firing,
|
|
ConditionEvals: res.ConditionEvals,
|
|
State: res.Rule.State,
|
|
}
|
|
|
|
if res.Error != nil {
|
|
dtoRes.Error = res.Error.Error()
|
|
}
|
|
|
|
for _, log := range res.Logs {
|
|
dtoRes.Logs = append(dtoRes.Logs, &dtos.AlertTestResultLog{Message: log.Message, Data: log.Data})
|
|
}
|
|
for _, match := range res.EvalMatches {
|
|
dtoRes.EvalMatches = append(dtoRes.EvalMatches, &dtos.EvalMatch{Metric: match.Metric, Value: match.Value})
|
|
}
|
|
|
|
dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs())
|
|
|
|
return response.JSON(http.StatusOK, dtoRes)
|
|
}
|
|
|
|
// swagger:route GET /alerts/{alert_id} legacy_alerts getAlertByID
|
|
//
|
|
// Get alert by ID.
|
|
//
|
|
// “evalMatches” data in the response is cached in the db when and only when the state of the alert changes (e.g. transitioning from “ok” to “alerting” state).
|
|
// If data from one server triggers the alert first and, before that server is seen leaving alerting state, a second server also enters a state that would trigger the alert, the second server will not be visible in “evalMatches” data.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertResponse
|
|
// 401: unauthorisedError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetAlert(c *contextmodel.ReqContext) response.Response {
|
|
id, err := strconv.ParseInt(web.Params(c.Req)[":alertId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "alertId is invalid", err)
|
|
}
|
|
query := alertmodels.GetAlertByIdQuery{ID: id}
|
|
|
|
res, err := hs.AlertEngine.AlertStore.GetAlertById(c.Req.Context(), &query)
|
|
if err != nil {
|
|
return response.Error(500, "List alerts failed", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, &res)
|
|
}
|
|
|
|
func (hs *HTTPServer) GetLegacyAlertNotifiers() func(*contextmodel.ReqContext) response.Response {
|
|
return func(_ *contextmodel.ReqContext) response.Response {
|
|
return response.JSON(http.StatusOK, alerting.GetNotifiers())
|
|
}
|
|
}
|
|
|
|
func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) response.Response {
|
|
return func(_ *contextmodel.ReqContext) response.Response {
|
|
return response.JSON(http.StatusOK, channels_config.GetAvailableNotifiers())
|
|
}
|
|
}
|
|
|
|
// swagger:route GET /alert-notifications/lookup legacy_alerts_notification_channels getAlertNotificationLookup
|
|
//
|
|
// Get all notification channels (lookup).
|
|
//
|
|
// Returns all notification channels, but with less detailed information. Accessible by any authenticated user and is mainly used by providing alert notification channels in Grafana UI when configuring alert rule.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertNotificationLookupResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetAlertNotificationLookup(c *contextmodel.ReqContext) response.Response {
|
|
alertNotifications, err := hs.getAlertNotificationsInternal(c)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get alert notifications", err)
|
|
}
|
|
|
|
result := make([]*dtos.AlertNotificationLookup, 0)
|
|
|
|
for _, notification := range alertNotifications {
|
|
result = append(result, dtos.NewAlertNotificationLookup(notification))
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// swagger:route GET /alert-notifications legacy_alerts_notification_channels getAlertNotificationChannels
|
|
//
|
|
// Get all notification channels.
|
|
//
|
|
// Returns all notification channels that the authenticated user has permission to view.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertNotificationChannelsResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetAlertNotifications(c *contextmodel.ReqContext) response.Response {
|
|
alertNotifications, err := hs.getAlertNotificationsInternal(c)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get alert notifications", err)
|
|
}
|
|
|
|
result := make([]*dtos.AlertNotification, 0)
|
|
|
|
for _, notification := range alertNotifications {
|
|
result = append(result, dtos.NewAlertNotification(notification))
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (hs *HTTPServer) getAlertNotificationsInternal(c *contextmodel.ReqContext) ([]*alertmodels.AlertNotification, error) {
|
|
query := &alertmodels.GetAllAlertNotificationsQuery{OrgID: c.SignedInUser.GetOrgID()}
|
|
return hs.AlertNotificationService.GetAllAlertNotifications(c.Req.Context(), query)
|
|
}
|
|
|
|
// swagger:route GET /alert-notifications/{notification_channel_id} legacy_alerts_notification_channels getAlertNotificationChannelByID
|
|
//
|
|
// Get notification channel by ID.
|
|
//
|
|
// Returns the notification channel given the notification channel ID.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertNotificationChannelResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetAlertNotificationByID(c *contextmodel.ReqContext) response.Response {
|
|
notificationId, err := strconv.ParseInt(web.Params(c.Req)[":notificationId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "notificationId is invalid", err)
|
|
}
|
|
query := &alertmodels.GetAlertNotificationsQuery{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
ID: notificationId,
|
|
}
|
|
|
|
if query.ID == 0 {
|
|
return response.Error(404, "Alert notification not found", nil)
|
|
}
|
|
|
|
res, err := hs.AlertNotificationService.GetAlertNotifications(c.Req.Context(), query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get alert notifications", err)
|
|
}
|
|
|
|
if res == nil {
|
|
return response.Error(404, "Alert notification not found", nil)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, dtos.NewAlertNotification(res))
|
|
}
|
|
|
|
// swagger:route GET /alert-notifications/uid/{notification_channel_uid} legacy_alerts_notification_channels getAlertNotificationChannelByUID
|
|
//
|
|
// Get notification channel by UID.
|
|
//
|
|
// Returns the notification channel given the notification channel UID.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertNotificationChannelResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) GetAlertNotificationByUID(c *contextmodel.ReqContext) response.Response {
|
|
query := &alertmodels.GetAlertNotificationsWithUidQuery{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
UID: web.Params(c.Req)[":uid"],
|
|
}
|
|
|
|
if query.UID == "" {
|
|
return response.Error(404, "Alert notification not found", nil)
|
|
}
|
|
|
|
res, err := hs.AlertNotificationService.GetAlertNotificationsWithUid(c.Req.Context(), query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get alert notifications", err)
|
|
}
|
|
|
|
if res == nil {
|
|
return response.Error(404, "Alert notification not found", nil)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, dtos.NewAlertNotification(res))
|
|
}
|
|
|
|
// swagger:route POST /alert-notifications legacy_alerts_notification_channels createAlertNotificationChannel
|
|
//
|
|
// Create notification channel.
|
|
//
|
|
// You can find the full list of [supported notifiers](https://grafana.com/docs/grafana/latest/alerting/old-alerting/notifications/#list-of-supported-notifiers) on the alert notifiers page.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertNotificationChannelResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 409: conflictError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) CreateAlertNotification(c *contextmodel.ReqContext) response.Response {
|
|
cmd := alertmodels.CreateAlertNotificationCommand{}
|
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
cmd.OrgID = c.SignedInUser.GetOrgID()
|
|
|
|
res, err := hs.AlertNotificationService.CreateAlertNotificationCommand(c.Req.Context(), &cmd)
|
|
if err != nil {
|
|
if errors.Is(err, alertmodels.ErrAlertNotificationWithSameNameExists) || errors.Is(err, alertmodels.ErrAlertNotificationWithSameUIDExists) {
|
|
return response.Error(409, "Failed to create alert notification", err)
|
|
}
|
|
var alertingErr alerting.ValidationError
|
|
if errors.As(err, &alertingErr) {
|
|
return response.Error(400, err.Error(), err)
|
|
}
|
|
return response.Error(500, "Failed to create alert notification", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, dtos.NewAlertNotification(res))
|
|
}
|
|
|
|
// swagger:route PUT /alert-notifications/{notification_channel_id} legacy_alerts_notification_channels updateAlertNotificationChannel
|
|
//
|
|
// Update notification channel by ID.
|
|
//
|
|
// Updates an existing notification channel identified by ID.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertNotificationChannelResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) UpdateAlertNotification(c *contextmodel.ReqContext) response.Response {
|
|
cmd := alertmodels.UpdateAlertNotificationCommand{}
|
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
cmd.OrgID = c.SignedInUser.GetOrgID()
|
|
|
|
err := hs.fillWithSecureSettingsData(c.Req.Context(), &cmd)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to update alert notification", err)
|
|
}
|
|
|
|
if _, err := hs.AlertNotificationService.UpdateAlertNotification(c.Req.Context(), &cmd); err != nil {
|
|
if errors.Is(err, alertmodels.ErrAlertNotificationNotFound) {
|
|
return response.Error(404, err.Error(), err)
|
|
}
|
|
var alertingErr alerting.ValidationError
|
|
if errors.As(err, &alertingErr) {
|
|
return response.Error(400, err.Error(), err)
|
|
}
|
|
return response.Error(500, "Failed to update alert notification", err)
|
|
}
|
|
|
|
query := alertmodels.GetAlertNotificationsQuery{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
ID: cmd.ID,
|
|
}
|
|
|
|
res, err := hs.AlertNotificationService.GetAlertNotifications(c.Req.Context(), &query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get alert notification", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, dtos.NewAlertNotification(res))
|
|
}
|
|
|
|
// swagger:route PUT /alert-notifications/uid/{notification_channel_uid} legacy_alerts_notification_channels updateAlertNotificationChannelByUID
|
|
//
|
|
// Update notification channel by UID.
|
|
//
|
|
// Updates an existing notification channel identified by uid.
|
|
//
|
|
// Responses:
|
|
// 200: getAlertNotificationChannelResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) UpdateAlertNotificationByUID(c *contextmodel.ReqContext) response.Response {
|
|
cmd := alertmodels.UpdateAlertNotificationWithUidCommand{}
|
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
cmd.OrgID = c.SignedInUser.GetOrgID()
|
|
cmd.UID = web.Params(c.Req)[":uid"]
|
|
|
|
err := hs.fillWithSecureSettingsDataByUID(c.Req.Context(), &cmd)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to update alert notification", err)
|
|
}
|
|
|
|
if _, err := hs.AlertNotificationService.UpdateAlertNotificationWithUid(c.Req.Context(), &cmd); err != nil {
|
|
if errors.Is(err, alertmodels.ErrAlertNotificationNotFound) {
|
|
return response.Error(404, err.Error(), nil)
|
|
}
|
|
return response.Error(500, "Failed to update alert notification", err)
|
|
}
|
|
|
|
query := alertmodels.GetAlertNotificationsWithUidQuery{
|
|
OrgID: cmd.OrgID,
|
|
UID: cmd.UID,
|
|
}
|
|
|
|
res, err := hs.AlertNotificationService.GetAlertNotificationsWithUid(c.Req.Context(), &query)
|
|
if err != nil {
|
|
return response.Error(500, "Failed to get alert notification", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, dtos.NewAlertNotification(res))
|
|
}
|
|
|
|
func (hs *HTTPServer) fillWithSecureSettingsData(ctx context.Context, cmd *alertmodels.UpdateAlertNotificationCommand) error {
|
|
if len(cmd.SecureSettings) == 0 {
|
|
return nil
|
|
}
|
|
|
|
query := &alertmodels.GetAlertNotificationsQuery{
|
|
OrgID: cmd.OrgID,
|
|
ID: cmd.ID,
|
|
}
|
|
|
|
res, err := hs.AlertNotificationService.GetAlertNotifications(ctx, query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, res.SecureSettings, setting.SecretKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for k, v := range secureSettings {
|
|
if _, ok := cmd.SecureSettings[k]; !ok {
|
|
cmd.SecureSettings[k] = v
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (hs *HTTPServer) fillWithSecureSettingsDataByUID(ctx context.Context, cmd *alertmodels.UpdateAlertNotificationWithUidCommand) error {
|
|
if len(cmd.SecureSettings) == 0 {
|
|
return nil
|
|
}
|
|
|
|
query := &alertmodels.GetAlertNotificationsWithUidQuery{
|
|
OrgID: cmd.OrgID,
|
|
UID: cmd.UID,
|
|
}
|
|
|
|
res, err := hs.AlertNotificationService.GetAlertNotificationsWithUid(ctx, query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, res.SecureSettings, setting.SecretKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for k, v := range secureSettings {
|
|
if _, ok := cmd.SecureSettings[k]; !ok {
|
|
cmd.SecureSettings[k] = v
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// swagger:route DELETE /alert-notifications/{notification_channel_id} legacy_alerts_notification_channels deleteAlertNotificationChannel
|
|
//
|
|
// Delete alert notification by ID.
|
|
//
|
|
// Deletes an existing notification channel identified by ID.
|
|
//
|
|
// Responses:
|
|
// 200: okResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) DeleteAlertNotification(c *contextmodel.ReqContext) response.Response {
|
|
notificationId, err := strconv.ParseInt(web.Params(c.Req)[":notificationId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "notificationId is invalid", err)
|
|
}
|
|
|
|
cmd := alertmodels.DeleteAlertNotificationCommand{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
ID: notificationId,
|
|
}
|
|
|
|
if err := hs.AlertNotificationService.DeleteAlertNotification(c.Req.Context(), &cmd); err != nil {
|
|
if errors.Is(err, alertmodels.ErrAlertNotificationNotFound) {
|
|
return response.Error(404, err.Error(), nil)
|
|
}
|
|
return response.Error(500, "Failed to delete alert notification", err)
|
|
}
|
|
|
|
return response.Success("Notification deleted")
|
|
}
|
|
|
|
// swagger:route DELETE /alert-notifications/uid/{notification_channel_uid} legacy_alerts_notification_channels deleteAlertNotificationChannelByUID
|
|
//
|
|
// Delete alert notification by UID.
|
|
//
|
|
// Deletes an existing notification channel identified by UID.
|
|
//
|
|
// Responses:
|
|
// 200: deleteAlertNotificationChannelResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) DeleteAlertNotificationByUID(c *contextmodel.ReqContext) response.Response {
|
|
cmd := alertmodels.DeleteAlertNotificationWithUidCommand{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
UID: web.Params(c.Req)[":uid"],
|
|
}
|
|
|
|
if err := hs.AlertNotificationService.DeleteAlertNotificationWithUid(c.Req.Context(), &cmd); err != nil {
|
|
if errors.Is(err, alertmodels.ErrAlertNotificationNotFound) {
|
|
return response.Error(404, err.Error(), nil)
|
|
}
|
|
return response.Error(500, "Failed to delete alert notification", err)
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, util.DynMap{
|
|
"message": "Notification deleted",
|
|
"id": cmd.DeletedAlertNotificationID,
|
|
})
|
|
}
|
|
|
|
// swagger:route POST /alert-notifications/test legacy_alerts_notification_channels notificationChannelTest
|
|
//
|
|
// Test notification channel.
|
|
//
|
|
// Sends a test notification to the channel.
|
|
//
|
|
// Responses:
|
|
// 200: okResponse
|
|
// 400: badRequestError
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 412: SMTPNotEnabledError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) NotificationTest(c *contextmodel.ReqContext) response.Response {
|
|
dto := dtos.NotificationTestCommand{}
|
|
if err := web.Bind(c.Req, &dto); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
cmd := &alerting.NotificationTestCommand{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
ID: dto.ID,
|
|
Name: dto.Name,
|
|
Type: dto.Type,
|
|
Settings: dto.Settings,
|
|
SecureSettings: dto.SecureSettings,
|
|
}
|
|
|
|
if err := hs.AlertNotificationService.HandleNotificationTestCommand(c.Req.Context(), cmd); err != nil {
|
|
if errors.Is(err, notifications.ErrSmtpNotEnabled) {
|
|
return response.Error(412, err.Error(), err)
|
|
}
|
|
var alertingErr alerting.ValidationError
|
|
if errors.As(err, &alertingErr) {
|
|
return response.Error(400, err.Error(), err)
|
|
}
|
|
|
|
return response.Error(500, "Failed to send alert notifications", err)
|
|
}
|
|
|
|
return response.Success("Test notification sent")
|
|
}
|
|
|
|
// swagger:route POST /alerts/{alert_id}/pause legacy_alerts pauseAlert
|
|
//
|
|
// Pause/unpause alert by id.
|
|
//
|
|
// Responses:
|
|
// 200: pauseAlertResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 404: notFoundError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) PauseAlert(legacyAlertingEnabled *bool) func(c *contextmodel.ReqContext) response.Response {
|
|
if legacyAlertingEnabled == nil || !*legacyAlertingEnabled {
|
|
return func(_ *contextmodel.ReqContext) response.Response {
|
|
return response.Error(http.StatusBadRequest, "legacy alerting is disabled, so this call has no effect.", nil)
|
|
}
|
|
}
|
|
|
|
return func(c *contextmodel.ReqContext) response.Response {
|
|
dto := dtos.PauseAlertCommand{}
|
|
if err := web.Bind(c.Req, &dto); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
alertID, err := strconv.ParseInt(web.Params(c.Req)[":alertId"], 10, 64)
|
|
if err != nil {
|
|
return response.Error(http.StatusBadRequest, "alertId is invalid", err)
|
|
}
|
|
result := make(map[string]any)
|
|
result["alertId"] = alertID
|
|
|
|
query := alertmodels.GetAlertByIdQuery{ID: alertID}
|
|
res, err := hs.AlertEngine.AlertStore.GetAlertById(c.Req.Context(), &query)
|
|
if err != nil {
|
|
return response.Error(500, "Get Alert failed", err)
|
|
}
|
|
|
|
guardian, err := guardian.New(c.Req.Context(), res.DashboardID, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
|
if err != nil {
|
|
return response.ErrOrFallback(http.StatusInternalServerError, "Error while creating permission guardian", err)
|
|
}
|
|
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
|
|
if err != nil {
|
|
return response.Error(500, "Error while checking permissions for Alert", err)
|
|
}
|
|
|
|
return response.Error(403, "Access denied to this dashboard and alert", nil)
|
|
}
|
|
|
|
// Alert state validation
|
|
if res.State != alertmodels.AlertStatePaused && !dto.Paused {
|
|
result["state"] = "un-paused"
|
|
result["message"] = "Alert is already un-paused"
|
|
return response.JSON(http.StatusOK, result)
|
|
} else if res.State == alertmodels.AlertStatePaused && dto.Paused {
|
|
result["state"] = alertmodels.AlertStatePaused
|
|
result["message"] = "Alert is already paused"
|
|
return response.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
cmd := alertmodels.PauseAlertCommand{
|
|
OrgID: c.SignedInUser.GetOrgID(),
|
|
AlertIDs: []int64{alertID},
|
|
Paused: dto.Paused,
|
|
}
|
|
|
|
if err := hs.AlertEngine.AlertStore.PauseAlert(c.Req.Context(), &cmd); err != nil {
|
|
return response.Error(500, "", err)
|
|
}
|
|
|
|
resp := alertmodels.AlertStateUnknown
|
|
pausedState := "un-paused"
|
|
if cmd.Paused {
|
|
resp = alertmodels.AlertStatePaused
|
|
pausedState = "paused"
|
|
}
|
|
|
|
result["state"] = resp
|
|
result["message"] = "Alert " + pausedState
|
|
return response.JSON(http.StatusOK, result)
|
|
}
|
|
}
|
|
|
|
// swagger:route POST /admin/pause-all-alerts admin pauseAllAlerts
|
|
//
|
|
// Pause/unpause all (legacy) alerts.
|
|
//
|
|
// Security:
|
|
// - basic:
|
|
//
|
|
// Responses:
|
|
// 200: pauseAlertsResponse
|
|
// 401: unauthorisedError
|
|
// 403: forbiddenError
|
|
// 500: internalServerError
|
|
func (hs *HTTPServer) PauseAllAlerts(legacyAlertingEnabled *bool) func(c *contextmodel.ReqContext) response.Response {
|
|
if legacyAlertingEnabled == nil || !*legacyAlertingEnabled {
|
|
return func(_ *contextmodel.ReqContext) response.Response {
|
|
return response.Error(http.StatusBadRequest, "legacy alerting is disabled, so this call has no effect.", nil)
|
|
}
|
|
}
|
|
|
|
return func(c *contextmodel.ReqContext) response.Response {
|
|
dto := dtos.PauseAllAlertsCommand{}
|
|
if err := web.Bind(c.Req, &dto); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
updateCmd := alertmodels.PauseAllAlertCommand{
|
|
Paused: dto.Paused,
|
|
}
|
|
|
|
if err := hs.AlertEngine.AlertStore.PauseAllAlerts(c.Req.Context(), &updateCmd); err != nil {
|
|
return response.Error(500, "Failed to pause alerts", err)
|
|
}
|
|
|
|
resp := alertmodels.AlertStatePending
|
|
pausedState := "un paused"
|
|
if updateCmd.Paused {
|
|
resp = alertmodels.AlertStatePaused
|
|
pausedState = "paused"
|
|
}
|
|
|
|
result := map[string]any{
|
|
"state": resp,
|
|
"message": "alerts " + pausedState,
|
|
"alertsAffected": updateCmd.ResultCount,
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, result)
|
|
}
|
|
}
|
|
|
|
// swagger:parameters pauseAllAlerts
|
|
type PauseAllAlertsParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body dtos.PauseAllAlertsCommand `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters deleteAlertNotificationChannel
|
|
type DeleteAlertNotificationChannelParams struct {
|
|
// in:path
|
|
// required:true
|
|
NotificationID int64 `json:"notification_channel_id"`
|
|
}
|
|
|
|
// swagger:parameters getAlertNotificationChannelByID
|
|
type GetAlertNotificationChannelByIDParams struct {
|
|
// in:path
|
|
// required:true
|
|
NotificationID int64 `json:"notification_channel_id"`
|
|
}
|
|
|
|
// swagger:parameters deleteAlertNotificationChannelByUID
|
|
type DeleteAlertNotificationChannelByUIDParams struct {
|
|
// in:path
|
|
// required:true
|
|
NotificationUID string `json:"notification_channel_uid"`
|
|
}
|
|
|
|
// swagger:parameters getAlertNotificationChannelByUID
|
|
type GetAlertNotificationChannelByUIDParams struct {
|
|
// in:path
|
|
// required:true
|
|
NotificationUID string `json:"notification_channel_uid"`
|
|
}
|
|
|
|
// swagger:parameters notificationChannelTest
|
|
type NotificationChannelTestParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body dtos.NotificationTestCommand `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters createAlertNotificationChannel
|
|
type CreateAlertNotificationChannelParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body alertmodels.CreateAlertNotificationCommand `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters updateAlertNotificationChannel
|
|
type UpdateAlertNotificationChannelParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body alertmodels.UpdateAlertNotificationCommand `json:"body"`
|
|
// in:path
|
|
// required:true
|
|
NotificationID int64 `json:"notification_channel_id"`
|
|
}
|
|
|
|
// swagger:parameters updateAlertNotificationChannelByUID
|
|
type UpdateAlertNotificationChannelByUIDParams struct {
|
|
// in:body
|
|
// required:true
|
|
Body alertmodels.UpdateAlertNotificationWithUidCommand `json:"body"`
|
|
// in:path
|
|
// required:true
|
|
NotificationUID string `json:"notification_channel_uid"`
|
|
}
|
|
|
|
// swagger:parameters getAlertByID
|
|
type GetAlertByIDParams struct {
|
|
// in:path
|
|
// required:true
|
|
AlertID string `json:"alert_id"`
|
|
}
|
|
|
|
// swagger:parameters pauseAlert
|
|
type PauseAlertParams struct {
|
|
// in:path
|
|
// required:true
|
|
AlertID string `json:"alert_id"`
|
|
// in:body
|
|
// required:true
|
|
Body dtos.PauseAlertCommand `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters getAlerts
|
|
type GetAlertsParams struct {
|
|
// Limit response to alerts in specified dashboard(s). You can specify multiple dashboards.
|
|
// in:query
|
|
// required:false
|
|
DashboardID []string `json:"dashboardId"`
|
|
// Limit response to alert for a specified panel on a dashboard.
|
|
// in:query
|
|
// required:false
|
|
PanelID int64 `json:"panelId"`
|
|
// Limit response to alerts having a name like this value.
|
|
// in:query
|
|
// required: false
|
|
Query string `json:"query"`
|
|
// Return alerts with one or more of the following alert states
|
|
// in:query
|
|
// required:false
|
|
// Description:
|
|
// * `all`
|
|
// * `no_data`
|
|
// * `paused`
|
|
// * `alerting`
|
|
// * `ok`
|
|
// * `pending`
|
|
// * `unknown`
|
|
// enum: all,no_data,paused,alerting,ok,pending,unknown
|
|
State string `json:"state"`
|
|
// Limit response to X number of alerts.
|
|
// in:query
|
|
// required:false
|
|
Limit int64 `json:"limit"`
|
|
// Limit response to alerts of dashboards in specified folder(s). You can specify multiple folders
|
|
// in:query
|
|
// required:false
|
|
// type array
|
|
// collectionFormat: multi
|
|
//
|
|
// Deprecated: use FolderUID instead
|
|
FolderID []string `json:"folderId"`
|
|
// Limit response to alerts having a dashboard name like this value./ Limit response to alerts having a dashboard name like this value.
|
|
// in:query
|
|
// required:false
|
|
DashboardQuery string `json:"dashboardQuery"`
|
|
// Limit response to alerts of dashboards with specified tags. To do an “AND” filtering with multiple tags, specify the tags parameter multiple times
|
|
// in:query
|
|
// required:false
|
|
// type: array
|
|
// collectionFormat: multi
|
|
DashboardTag []string `json:"dashboardTag"`
|
|
}
|
|
|
|
// swagger:parameters testAlert
|
|
type TestAlertParams struct {
|
|
// in:body
|
|
Body dtos.AlertTestCommand `json:"body"`
|
|
}
|
|
|
|
// swagger:parameters getDashboardStates
|
|
type GetDashboardStatesParams struct {
|
|
// in:query
|
|
// required: true
|
|
DashboardID int64 `json:"dashboardId"`
|
|
}
|
|
|
|
// swagger:response pauseAlertsResponse
|
|
type PauseAllAlertsResponse struct {
|
|
// in:body
|
|
Body struct {
|
|
// AlertsAffected is the number of the affected alerts.
|
|
// required: true
|
|
AlertsAffected int64 `json:"alertsAffected"`
|
|
// required: true
|
|
Message string `json:"message"`
|
|
// Alert result state
|
|
// required true
|
|
State string `json:"state"`
|
|
} `json:"body"`
|
|
}
|
|
|
|
// swagger:response getAlertNotificationChannelsResponse
|
|
type GetAlertNotificationChannelsResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body []*dtos.AlertNotification `json:"body"`
|
|
}
|
|
|
|
// swagger:response getAlertNotificationLookupResponse
|
|
type LookupAlertNotificationChannelsResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body []*dtos.AlertNotificationLookup `json:"body"`
|
|
}
|
|
|
|
// swagger:response getAlertNotificationChannelResponse
|
|
type GetAlertNotificationChannelResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body *dtos.AlertNotification `json:"body"`
|
|
}
|
|
|
|
// swagger:response deleteAlertNotificationChannelResponse
|
|
type DeleteAlertNotificationChannelResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body struct {
|
|
// ID Identifier of the deleted notification channel.
|
|
// required: true
|
|
// example: 65
|
|
ID int64 `json:"id"`
|
|
|
|
// Message Message of the deleted notificatiton channel.
|
|
// required: true
|
|
Message string `json:"message"`
|
|
} `json:"body"`
|
|
}
|
|
|
|
// swagger:response SMTPNotEnabledError
|
|
type SMTPNotEnabledError PreconditionFailedError
|
|
|
|
// swagger:response getAlertsResponse
|
|
type GetAlertsResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body []*alertmodels.AlertListItemDTO `json:"body"`
|
|
}
|
|
|
|
// swagger:response getAlertResponse
|
|
type GetAlertResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body *alertmodels.Alert `json:"body"`
|
|
}
|
|
|
|
// swagger:response pauseAlertResponse
|
|
type PauseAlertResponse struct {
|
|
// in:body
|
|
Body struct {
|
|
// required: true
|
|
AlertID int64 `json:"alertId"`
|
|
// required: true
|
|
Message string `json:"message"`
|
|
// Alert result state
|
|
// required true
|
|
State string `json:"state"`
|
|
} `json:"body"`
|
|
}
|
|
|
|
// swagger:response testAlertResponse
|
|
type TestAlertResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body *dtos.AlertTestResult `json:"body"`
|
|
}
|
|
|
|
// swagger:response getDashboardStatesResponse
|
|
type GetDashboardStatesResponse struct {
|
|
// The response message
|
|
// in: body
|
|
Body []*alertmodels.AlertStateInfoDTO `json:"body"`
|
|
}
|