mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add notification policy provisioning file export (#70009)
* Alerting: Add notification policy provisioning file export - Add provisioning API endpoint for exporting notification policies. - Add option in notification policy view ellipsis dropdown for exporting. - Update various provisioning documentation.
This commit is contained in:
parent
4c42632ab8
commit
cfb1656968
@ -496,11 +496,13 @@ settings:
|
||||
|
||||
Create or reset the notification policy tree in your Grafana instance(s).
|
||||
|
||||
1. Create a YAML or JSON configuration file.
|
||||
1. Create a notification policy in Grafana.
|
||||
2. Use the [Alerting provisioning API]({{< relref "../../../../developers/http_api/alerting_provisioning" >}}) export endpoints to download a provisioning file for your notification policy.
|
||||
3. Copy the contents into a YAML or JSON configuration file in the default provisioning directory or in your configured directory.
|
||||
|
||||
Example configuration files can be found below.
|
||||
|
||||
2. Add the file(s) to your GitOps workflow, so that they deploy alongside your Grafana instance(s).
|
||||
4. Ensure that your files are in the right directory on the node running the Grafana server, so that they deploy alongside your Grafana instance(s).
|
||||
|
||||
Here is an example of a configuration file for creating notification policies.
|
||||
|
||||
|
@ -70,11 +70,12 @@ Contact point provisioning is for Grafana-managed alerts only.
|
||||
|
||||
### Notification policies
|
||||
|
||||
| Method | URI | Name | Summary |
|
||||
| ------ | ----------------------------- | --------------------------------------------------- | ------------------------------------ |
|
||||
| DELETE | /api/v1/provisioning/policies | [route reset policy tree](#route-reset-policy-tree) | Clears the notification policy tree. |
|
||||
| GET | /api/v1/provisioning/policies | [route get policy tree](#route-get-policy-tree) | Get the notification policy tree. |
|
||||
| PUT | /api/v1/provisioning/policies | [route put policy tree](#route-put-policy-tree) | Sets the notification policy tree. |
|
||||
| Method | URI | Name | Summary |
|
||||
| ------ | ------------------------------------ | ------------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| DELETE | /api/v1/provisioning/policies | [route reset policy tree](#route-reset-policy-tree) | Clears the notification policy tree. |
|
||||
| GET | /api/v1/provisioning/policies | [route get policy tree](#route-get-policy-tree) | Get the notification policy tree. |
|
||||
| GET | /api/v1/provisioning/policies/export | [route get policy tree export](#route-get-policy-tree-export) | Export the notification policy tree in provisioning file format. |
|
||||
| PUT | /api/v1/provisioning/policies | [route put policy tree](#route-put-policy-tree) | Sets the notification policy tree. |
|
||||
|
||||
### Mute timings
|
||||
|
||||
@ -573,6 +574,37 @@ Status: OK
|
||||
|
||||
[Route](#route)
|
||||
|
||||
### <span id="route-get-policy-tree-export"></span> Export the notification policy tree in provisioning file format. (_RouteGetPolicyTreeExport_)
|
||||
|
||||
```
|
||||
GET /api/v1/provisioning/policies/export
|
||||
```
|
||||
|
||||
#### All responses
|
||||
|
||||
| Code | Status | Description | Has headers | Schema |
|
||||
| ---------------------------------------- | --------- | ------------------ | :---------: | -------------------------------------------------- |
|
||||
| [200](#route-get-policy-tree-export-200) | OK | AlertingFileExport | | [schema](#route-get-policy-tree-export-200-schema) |
|
||||
| [404](#route-get-policy-tree-export-404) | Not Found | NotFound | | [schema](#route-get-policy-tree-export-404-schema) |
|
||||
|
||||
#### Responses
|
||||
|
||||
##### <span id="route-get-policy-tree-export-200"></span> 200 - AlertingFileExport
|
||||
|
||||
Status: OK
|
||||
|
||||
###### <span id="route-get-policy-tree-export-200-schema"></span> Schema
|
||||
|
||||
[AlertingFileExport](#alerting-file-export)
|
||||
|
||||
##### <span id="route-get-policy-tree-export-404"></span> 404 - NotFound
|
||||
|
||||
Status: Not Found
|
||||
|
||||
###### <span id="route-get-policy-tree-export-404-schema"></span> Schema
|
||||
|
||||
[NotFound](#not-found)
|
||||
|
||||
### <span id="route-get-template"></span> Get a notification template. (_RouteGetTemplate_)
|
||||
|
||||
```
|
||||
@ -1182,11 +1214,12 @@ Status: Accepted
|
||||
|
||||
{{% responsive-table %}}
|
||||
|
||||
| Name | Type | Go type | Required | Default | Description | Example |
|
||||
| ------------- | -------------------------------------------------- | ------------------------- | :------: | ------- | ----------- | ------- |
|
||||
| apiVersion | int64 (formatted integer) | `int64` | | | | |
|
||||
| contactPoints | [][ContactPointExport](#contact-point-export) | `[]*ContactPointExport` | | | | |
|
||||
| groups | [][AlertRuleGroupExport](#alert-rule-group-export) | `[]*AlertRuleGroupExport` | | | | |
|
||||
| Name | Type | Go type | Required | Default | Description | Example |
|
||||
| ------------- | --------------------------------------------------------- | ----------------------------- | :------: | ------- | ----------- | ------- |
|
||||
| apiVersion | int64 (formatted integer) | `int64` | | | | |
|
||||
| contactPoints | [][ContactPointExport](#contact-point-export) | `[]*ContactPointExport` | | | | |
|
||||
| groups | [][AlertRuleGroupExport](#alert-rule-group-export) | `[]*AlertRuleGroupExport` | | | | |
|
||||
| policies | [][NotificationPolicyExport](#notification-policy-export) | `[]*NotificationPolicyExport` | | | | |
|
||||
|
||||
{{% /responsive-table %}}
|
||||
|
||||
@ -1285,6 +1318,19 @@ Status: Accepted
|
||||
|
||||
[][MuteTimeInterval](#mute-time-interval)
|
||||
|
||||
### <span id="not-found"></span> NotFound
|
||||
|
||||
[interface{}](#interface)
|
||||
|
||||
### <span id="notification-policy-export"></span> NotificationPolicyExport
|
||||
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Go type | Required | Default | Description | Example |
|
||||
| ------ | ---------------------------- | ------------- | :------: | ------- | ----------- | ------- |
|
||||
| Policy | [RouteExport](#route-export) | `RouteExport` | | | inline | |
|
||||
| orgId | int64 (formatted integer) | `int64` | | | | |
|
||||
|
||||
### <span id="notification-template"></span> NotificationTemplate
|
||||
|
||||
**Properties**
|
||||
@ -1362,16 +1408,20 @@ Status: Accepted
|
||||
|
||||
[][ProvisionedAlertRule](#provisioned-alert-rule)
|
||||
|
||||
### <span id="raw-message"></span> RawMessage
|
||||
|
||||
[interface{}](#interface)
|
||||
|
||||
### <span id="receiver-export"></span> ReceiverExport
|
||||
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Go type | Required | Default | Description | Example |
|
||||
| --------------------- | ------------- | -------- | :------: | ------- | ----------- | ------- |
|
||||
| disableResolveMessage | boolean | `bool` | | | | |
|
||||
| settings | [JSON](#json) | `JSON` | | | | |
|
||||
| type | string | `string` | | | | |
|
||||
| uid | string | `string` | | | | |
|
||||
| Name | Type | Go type | Required | Default | Description | Example |
|
||||
| --------------------- | -------------------------- | ------------ | :------: | ------- | ----------- | ------- |
|
||||
| disableResolveMessage | boolean | `bool` | | | | |
|
||||
| settings | [RawMessage](#raw-message) | `RawMessage` | | | | |
|
||||
| type | string | `string` | | | | |
|
||||
| uid | string | `string` | | | | |
|
||||
|
||||
### <span id="regexp"></span> Regexp
|
||||
|
||||
@ -1423,6 +1473,28 @@ Status: Accepted
|
||||
|
||||
{{% /responsive-table %}}
|
||||
|
||||
### <span id="route-export"></span> RouteExport
|
||||
|
||||
> RouteExport is the provisioned file export of definitions.Route. This is needed to hide fields that aren't useable in
|
||||
> provisioning file format. An alternative would be to define a custom MarshalJSON and MarshalYAML that excludes them.
|
||||
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Go type | Required | Default | Description | Example |
|
||||
| ------------------- | ---------------------------------- | ------------------- | :------: | ------- | --------------------------------------- | ------- |
|
||||
| continue | boolean | `bool` | | | | |
|
||||
| group_by | []string | `[]string` | | | | |
|
||||
| group_interval | string | `string` | | | | |
|
||||
| group_wait | string | `string` | | | | |
|
||||
| match | map of string | `map[string]string` | | | Deprecated. Remove before v1.0 release. | |
|
||||
| match_re | [MatchRegexps](#match-regexps) | `MatchRegexps` | | | | |
|
||||
| matchers | [Matchers](#matchers) | `Matchers` | | | | |
|
||||
| mute_time_intervals | []string | `[]string` | | | | |
|
||||
| object_matchers | [ObjectMatchers](#object-matchers) | `ObjectMatchers` | | | | |
|
||||
| receiver | string | `string` | | | | |
|
||||
| repeat_interval | string | `string` | | | | |
|
||||
| routes | [][RouteExport](#route-export) | `[]*RouteExport` | | | | |
|
||||
|
||||
### <span id="time-interval"></span> TimeInterval
|
||||
|
||||
> TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained
|
||||
|
@ -80,6 +80,23 @@ func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) respo
|
||||
return response.JSON(http.StatusOK, policies)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RouteGetPolicyTreeExport(c *contextmodel.ReqContext) response.Response {
|
||||
policies, err := srv.policies.GetPolicyTree(c.Req.Context(), c.OrgID)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
|
||||
e, err := AlertingFileExportFromRoute(c.OrgID, policies)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export")
|
||||
}
|
||||
|
||||
return exportResponse(c, e)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutPolicyTree(c *contextmodel.ReqContext, tree definitions.Route) response.Response {
|
||||
provenance := determineProvenance(c)
|
||||
err := srv.policies.UpdatePolicyTree(c.Req.Context(), c.OrgID, tree, alerting_models.Provenance(provenance))
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
prometheus "github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/alertmanager/timeinterval"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -812,6 +813,116 @@ func TestProvisioningApi(t *testing.T) {
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("notification policies", func(t *testing.T) {
|
||||
t.Run("are present, GET returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
})
|
||||
|
||||
t.Run("accept header contains yaml, GET returns text yaml", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json and yaml, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json, application/yaml")
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("query param download=true, GET returns content disposition attachment", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Form.Set("download", "true")
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Contains(t, rc.Context.Resp.Header().Get("Content-Disposition"), "attachment")
|
||||
})
|
||||
|
||||
t.Run("query param download=false, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Form.Set("download", "false")
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("query param download not set, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("json body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
sut.policies = createFakeNotificationPolicyService()
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||
expectedResponse := `{"apiVersion":1,"policies":[{"orgId":1,"Policy":{"receiver":"default-receiver","group_by":["g1","g2"],"routes":[{"receiver":"nested-receiver","group_by":["g3","g4"],"matchers":["a=\"b\""],"object_matchers":[["foo","=","bar"]],"mute_time_intervals":["interval"],"continue":true,"group_wait":"5m","group_interval":"5m","repeat_interval":"5m"}],"group_wait":"30s","group_interval":"5m","repeat_interval":"1h"}}]}`
|
||||
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
|
||||
t.Run("yaml body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
sut.policies = createFakeNotificationPolicyService()
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
expectedResponse := "apiVersion: 1\npolicies:\n - orgId: 1\n receiver: default-receiver\n group_by:\n - g1\n - g2\n routes:\n - receiver: nested-receiver\n group_by:\n - g3\n - g4\n matchers:\n - a=\"b\"\n object_matchers:\n - - foo\n - =\n - bar\n mute_time_intervals:\n - interval\n continue: true\n group_wait: 5m\n group_interval: 5m\n repeat_interval: 5m\n group_wait: 30s\n group_interval: 5m\n repeat_interval: 1h\n"
|
||||
|
||||
response := sut.RouteGetPolicyTreeExport(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1162,6 +1273,39 @@ func newFakeNotificationPolicyService() *fakeNotificationPolicyService {
|
||||
}
|
||||
}
|
||||
|
||||
func createFakeNotificationPolicyService() *fakeNotificationPolicyService {
|
||||
seconds := model.Duration(time.Duration(30) * time.Second)
|
||||
minutes := model.Duration(time.Duration(5) * time.Minute)
|
||||
hours := model.Duration(time.Duration(1) * time.Hour)
|
||||
return &fakeNotificationPolicyService{
|
||||
tree: definitions.Route{
|
||||
Receiver: "default-receiver",
|
||||
GroupByStr: []string{"g1", "g2"},
|
||||
GroupWait: &seconds,
|
||||
GroupInterval: &minutes,
|
||||
RepeatInterval: &hours,
|
||||
Routes: []*definitions.Route{{
|
||||
Receiver: "nested-receiver",
|
||||
GroupByStr: []string{"g3", "g4"},
|
||||
Matchers: prometheus.Matchers{
|
||||
{
|
||||
Name: "a",
|
||||
Type: labels.MatchEqual,
|
||||
Value: "b",
|
||||
},
|
||||
},
|
||||
ObjectMatchers: definitions.ObjectMatchers{{Type: 0, Name: "foo", Value: "bar"}},
|
||||
MuteTimeIntervals: []string{"interval"},
|
||||
Continue: true,
|
||||
GroupWait: &minutes,
|
||||
GroupInterval: &minutes,
|
||||
RepeatInterval: &minutes,
|
||||
}},
|
||||
},
|
||||
prov: models.ProvenanceAPI,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
if orgID != 1 {
|
||||
return definitions.Route{}, store.ErrNoAlertmanagerConfiguration
|
||||
|
@ -185,6 +185,7 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
|
||||
// Grafana-only Provisioning Read Paths
|
||||
case http.MethodGet + "/api/v1/provisioning/policies",
|
||||
http.MethodGet + "/api/v1/provisioning/policies/export",
|
||||
http.MethodGet + "/api/v1/provisioning/contact-points",
|
||||
http.MethodGet + "/api/v1/provisioning/contact-points/export",
|
||||
http.MethodGet + "/api/v1/provisioning/templates",
|
||||
|
@ -49,7 +49,7 @@ func TestAuthorize(t *testing.T) {
|
||||
}
|
||||
paths[p] = methods
|
||||
}
|
||||
require.Len(t, paths, 49)
|
||||
require.Len(t, paths, 50)
|
||||
|
||||
ac := acmock.New()
|
||||
api := &API{AccessControl: ac}
|
||||
|
@ -260,3 +260,41 @@ func ReceiverExportFromEmbeddedContactPoint(contact definitions.EmbeddedContactP
|
||||
DisableResolveMessage: contact.DisableResolveMessage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AlertingFileExportFromRoute creates a definitions.AlertingFileExport DTO from definitions.Route.
|
||||
func AlertingFileExportFromRoute(orgID int64, route definitions.Route) (definitions.AlertingFileExport, error) {
|
||||
f := definitions.AlertingFileExport{
|
||||
APIVersion: 1,
|
||||
Policies: []definitions.NotificationPolicyExport{{
|
||||
OrgID: orgID,
|
||||
Policy: RouteExportFromRoute(&route),
|
||||
}},
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// RouteExportFromRoute creates a definitions.RouteExport DTO from definitions.Route.
|
||||
func RouteExportFromRoute(route *definitions.Route) *definitions.RouteExport {
|
||||
export := definitions.RouteExport{
|
||||
Receiver: route.Receiver,
|
||||
GroupByStr: route.GroupByStr,
|
||||
Match: route.Match,
|
||||
MatchRE: route.MatchRE,
|
||||
Matchers: route.Matchers,
|
||||
ObjectMatchers: route.ObjectMatchers,
|
||||
MuteTimeIntervals: route.MuteTimeIntervals,
|
||||
Continue: route.Continue,
|
||||
GroupWait: route.GroupWait,
|
||||
GroupInterval: route.GroupInterval,
|
||||
RepeatInterval: route.RepeatInterval,
|
||||
}
|
||||
|
||||
if len(route.Routes) > 0 {
|
||||
export.Routes = make([]*definitions.RouteExport, 0, len(route.Routes))
|
||||
for _, r := range route.Routes {
|
||||
export.Routes = append(export.Routes, RouteExportFromRoute(r))
|
||||
}
|
||||
}
|
||||
|
||||
return &export
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ type ProvisioningApi interface {
|
||||
RouteGetMuteTiming(*contextmodel.ReqContext) response.Response
|
||||
RouteGetMuteTimings(*contextmodel.ReqContext) response.Response
|
||||
RouteGetPolicyTree(*contextmodel.ReqContext) response.Response
|
||||
RouteGetPolicyTreeExport(*contextmodel.ReqContext) response.Response
|
||||
RouteGetTemplate(*contextmodel.ReqContext) response.Response
|
||||
RouteGetTemplates(*contextmodel.ReqContext) response.Response
|
||||
RoutePostAlertRule(*contextmodel.ReqContext) response.Response
|
||||
@ -113,6 +114,9 @@ func (f *ProvisioningApiHandler) RouteGetMuteTimings(ctx *contextmodel.ReqContex
|
||||
func (f *ProvisioningApiHandler) RouteGetPolicyTree(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteGetPolicyTree(ctx)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetPolicyTreeExport(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteGetPolicyTreeExport(ctx)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetTemplate(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
nameParam := web.Params(ctx.Req)[":name"]
|
||||
@ -360,6 +364,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApi, m *metrics
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/policies/export"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/policies/export"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/v1/provisioning/policies/export",
|
||||
api.Hooks.Wrap(srv.RouteGetPolicyTreeExport),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/templates/{name}"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/templates/{name}"),
|
||||
|
@ -20,6 +20,10 @@ func (f *ProvisioningApiHandler) handleRouteGetPolicyTree(ctx *contextmodel.ReqC
|
||||
return f.svc.RouteGetPolicyTree(ctx)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRouteGetPolicyTreeExport(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.svc.RouteGetPolicyTreeExport(ctx)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRoutePutPolicyTree(ctx *contextmodel.ReqContext, route apimodels.Route) response.Response {
|
||||
return f.svc.RoutePutPolicyTree(ctx, route)
|
||||
}
|
||||
|
@ -297,6 +297,12 @@
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"policies": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/NotificationPolicyExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "AlertingFileExport is the full provisioned file export.",
|
||||
@ -1097,6 +1103,10 @@
|
||||
"description": "PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'.",
|
||||
"type": "string"
|
||||
},
|
||||
"preferredVisualisationPluginId": {
|
||||
"description": "PreferredVisualizationPluginId sets the panel plugin id to use to render the data when using Explore. If\nthe plugin cannot be found will fall back to PreferredVisualization.",
|
||||
"type": "string"
|
||||
},
|
||||
"preferredVisualisationType": {
|
||||
"$ref": "#/definitions/VisType"
|
||||
},
|
||||
@ -1897,6 +1907,19 @@
|
||||
"title": "NoticeSeverity is a type for the Severity property of a Notice.",
|
||||
"type": "integer"
|
||||
},
|
||||
"NotificationPolicyExport": {
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
},
|
||||
"orgId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
"type": "object"
|
||||
},
|
||||
"NotificationTemplate": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@ -2882,7 +2905,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/definitions/Json"
|
||||
"$ref": "#/definitions/RawMessage"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
@ -2985,6 +3008,61 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"RouteExport": {
|
||||
"description": "RouteExport is the provisioned file export of definitions.Route. This is needed to hide fields that aren't useable in\nprovisioning file format. An alternative would be to define a custom MarshalJSON and MarshalYAML that excludes them.",
|
||||
"properties": {
|
||||
"continue": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"group_by": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"group_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"group_wait": {
|
||||
"type": "string"
|
||||
},
|
||||
"match": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Deprecated. Remove before v1.0 release.",
|
||||
"type": "object"
|
||||
},
|
||||
"match_re": {
|
||||
"$ref": "#/definitions/MatchRegexps"
|
||||
},
|
||||
"matchers": {
|
||||
"$ref": "#/definitions/Matchers"
|
||||
},
|
||||
"mute_time_intervals": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Rule": {
|
||||
"description": "adapted from cortex",
|
||||
"properties": {
|
||||
@ -4185,7 +4263,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"integration": {
|
||||
"description": "Integration integration",
|
||||
"properties": {
|
||||
"lastNotifyAttempt": {
|
||||
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
||||
@ -5237,6 +5314,29 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/policies/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetPolicyTreeExport",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Export the notification policy tree in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/templates": {
|
||||
"get": {
|
||||
"operationId": "RouteGetTemplates",
|
||||
|
@ -3,9 +3,10 @@ package definitions
|
||||
// AlertingFileExport is the full provisioned file export.
|
||||
// swagger:model
|
||||
type AlertingFileExport struct {
|
||||
APIVersion int64 `json:"apiVersion" yaml:"apiVersion"`
|
||||
Groups []AlertRuleGroupExport `json:"groups,omitempty" yaml:"groups,omitempty"`
|
||||
ContactPoints []ContactPointExport `json:"contactPoints,omitempty" yaml:"contactPoints,omitempty"`
|
||||
APIVersion int64 `json:"apiVersion" yaml:"apiVersion"`
|
||||
Groups []AlertRuleGroupExport `json:"groups,omitempty" yaml:"groups,omitempty"`
|
||||
ContactPoints []ContactPointExport `json:"contactPoints,omitempty" yaml:"contactPoints,omitempty"`
|
||||
Policies []NotificationPolicyExport `json:"policies,omitempty" yaml:"policies,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetAlertRuleGroupExport RouteGetAlertRuleExport RouteGetAlertRulesExport RouteGetContactpointsExport RouteGetContactpointExport
|
||||
|
@ -51,7 +51,7 @@ import (
|
||||
// Responses:
|
||||
// 204: description: The contact point was deleted successfully.
|
||||
|
||||
// swagger:parameters RoutePutContactpoint RouteDeleteContactpoints RouteGetContactpoint RouteGetContactpointExport
|
||||
// swagger:parameters RoutePutContactpoint RouteDeleteContactpoints
|
||||
type ContactPointUIDReference struct {
|
||||
// UID is the contact point unique identifier
|
||||
// in:path
|
||||
|
@ -1,5 +1,10 @@
|
||||
package definitions
|
||||
|
||||
import (
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// swagger:route GET /api/v1/provisioning/policies provisioning stable RouteGetPolicyTree
|
||||
//
|
||||
// Get the notification policy tree.
|
||||
@ -29,9 +34,44 @@ package definitions
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
|
||||
// swagger:route GET /api/v1/provisioning/policies/export provisioning stable RouteGetPolicyTreeExport
|
||||
//
|
||||
// Export the notification policy tree in provisioning file format.
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertingFileExport
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:parameters RoutePutPolicyTree
|
||||
type Policytree struct {
|
||||
// The new notification routing tree to use
|
||||
// in:body
|
||||
Body Route
|
||||
}
|
||||
|
||||
// NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.
|
||||
type NotificationPolicyExport struct {
|
||||
OrgID int64 `json:"orgId" yaml:"orgId"`
|
||||
Policy *RouteExport `json:",inline" yaml:",inline"`
|
||||
}
|
||||
|
||||
// RouteExport is the provisioned file export of definitions.Route. This is needed to hide fields that aren't useable in
|
||||
// provisioning file format. An alternative would be to define a custom MarshalJSON and MarshalYAML that excludes them.
|
||||
type RouteExport struct {
|
||||
Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty"`
|
||||
|
||||
GroupByStr []string `yaml:"group_by,omitempty" json:"group_by,omitempty"`
|
||||
// Deprecated. Remove before v1.0 release.
|
||||
Match map[string]string `yaml:"match,omitempty" json:"match,omitempty"`
|
||||
// Deprecated. Remove before v1.0 release.
|
||||
MatchRE config.MatchRegexps `yaml:"match_re,omitempty" json:"match_re,omitempty"`
|
||||
Matchers config.Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
|
||||
ObjectMatchers ObjectMatchers `yaml:"object_matchers,omitempty" json:"object_matchers,omitempty"`
|
||||
MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
|
||||
Continue bool `yaml:"continue,omitempty" json:"continue,omitempty"` // Added omitempty to yaml for a cleaner export.
|
||||
Routes []*RouteExport `yaml:"routes,omitempty" json:"routes,omitempty"`
|
||||
|
||||
GroupWait *model.Duration `yaml:"group_wait,omitempty" json:"group_wait,omitempty"`
|
||||
GroupInterval *model.Duration `yaml:"group_interval,omitempty" json:"group_interval,omitempty"`
|
||||
RepeatInterval *model.Duration `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty"`
|
||||
}
|
||||
|
@ -297,6 +297,12 @@
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"policies": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/NotificationPolicyExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "AlertingFileExport is the full provisioned file export.",
|
||||
@ -1097,6 +1103,10 @@
|
||||
"description": "PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'.",
|
||||
"type": "string"
|
||||
},
|
||||
"preferredVisualisationPluginId": {
|
||||
"description": "PreferredVisualizationPluginId sets the panel plugin id to use to render the data when using Explore. If\nthe plugin cannot be found will fall back to PreferredVisualization.",
|
||||
"type": "string"
|
||||
},
|
||||
"preferredVisualisationType": {
|
||||
"$ref": "#/definitions/VisType"
|
||||
},
|
||||
@ -1897,6 +1907,19 @@
|
||||
"title": "NoticeSeverity is a type for the Severity property of a Notice.",
|
||||
"type": "integer"
|
||||
},
|
||||
"NotificationPolicyExport": {
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
},
|
||||
"orgId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
"type": "object"
|
||||
},
|
||||
"NotificationTemplate": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@ -2882,7 +2905,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/definitions/Json"
|
||||
"$ref": "#/definitions/RawMessage"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
@ -2985,6 +3008,61 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"RouteExport": {
|
||||
"description": "RouteExport is the provisioned file export of definitions.Route. This is needed to hide fields that aren't useable in\nprovisioning file format. An alternative would be to define a custom MarshalJSON and MarshalYAML that excludes them.",
|
||||
"properties": {
|
||||
"continue": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"group_by": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"group_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"group_wait": {
|
||||
"type": "string"
|
||||
},
|
||||
"match": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Deprecated. Remove before v1.0 release.",
|
||||
"type": "object"
|
||||
},
|
||||
"match_re": {
|
||||
"$ref": "#/definitions/MatchRegexps"
|
||||
},
|
||||
"matchers": {
|
||||
"$ref": "#/definitions/Matchers"
|
||||
},
|
||||
"mute_time_intervals": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Rule": {
|
||||
"description": "adapted from cortex",
|
||||
"properties": {
|
||||
@ -3940,6 +4018,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"properties": {
|
||||
"alerts": {
|
||||
"description": "alerts",
|
||||
@ -3963,6 +4042,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
@ -4067,7 +4147,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labelSet"
|
||||
@ -4328,7 +4407,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -7017,6 +7095,29 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/policies/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetPolicyTreeExport",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Export the notification policy tree in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/templates": {
|
||||
"get": {
|
||||
"operationId": "RouteGetTemplates",
|
||||
|
@ -2572,6 +2572,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/policies/export": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"provisioning",
|
||||
"stable"
|
||||
],
|
||||
"summary": "Export the notification policy tree in provisioning file format.",
|
||||
"operationId": "RouteGetPolicyTreeExport",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/templates": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -3118,6 +3142,12 @@
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
}
|
||||
},
|
||||
"policies": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/NotificationPolicyExport"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3921,6 +3951,10 @@
|
||||
"description": "PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'.",
|
||||
"type": "string"
|
||||
},
|
||||
"preferredVisualisationPluginId": {
|
||||
"description": "PreferredVisualizationPluginId sets the panel plugin id to use to render the data when using Explore. If\nthe plugin cannot be found will fall back to PreferredVisualization.",
|
||||
"type": "string"
|
||||
},
|
||||
"preferredVisualisationType": {
|
||||
"$ref": "#/definitions/VisType"
|
||||
},
|
||||
@ -4720,6 +4754,19 @@
|
||||
"format": "int64",
|
||||
"title": "NoticeSeverity is a type for the Severity property of a Notice."
|
||||
},
|
||||
"NotificationPolicyExport": {
|
||||
"type": "object",
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
},
|
||||
"orgId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NotificationTemplate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -5707,7 +5754,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/definitions/Json"
|
||||
"$ref": "#/definitions/RawMessage"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
@ -5808,6 +5855,61 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"RouteExport": {
|
||||
"description": "RouteExport is the provisioned file export of definitions.Route. This is needed to hide fields that aren't useable in\nprovisioning file format. An alternative would be to define a custom MarshalJSON and MarshalYAML that excludes them.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"continue": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"group_by": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"group_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"group_wait": {
|
||||
"type": "string"
|
||||
},
|
||||
"match": {
|
||||
"description": "Deprecated. Remove before v1.0 release.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"match_re": {
|
||||
"$ref": "#/definitions/MatchRegexps"
|
||||
},
|
||||
"matchers": {
|
||||
"$ref": "#/definitions/Matchers"
|
||||
},
|
||||
"mute_time_intervals": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rule": {
|
||||
"description": "adapted from cortex",
|
||||
"type": "object",
|
||||
@ -6763,6 +6865,7 @@
|
||||
}
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"alerts",
|
||||
@ -6787,6 +6890,7 @@
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
@ -6892,7 +6996,6 @@
|
||||
}
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"labels",
|
||||
@ -7158,7 +7261,6 @@
|
||||
}
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
|
@ -13,11 +13,14 @@ import ConditionalWrap from 'app/features/alerting/components/ConditionalWrap';
|
||||
import { RouteWithID, Receiver, ObjectMatcher, AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { ReceiversState } from 'app/types';
|
||||
|
||||
import { isOrgAdmin } from '../../../../plugins/admin/permissions';
|
||||
import { INTEGRATION_ICONS } from '../../types/contact-points';
|
||||
import { getNotificationsPermissions } from '../../utils/access-control';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||
import { normalizeMatchers } from '../../utils/matchers';
|
||||
import { createContactPointLink, createMuteTimingLink } from '../../utils/misc';
|
||||
import { getInheritedProperties, InhertitableProperties } from '../../utils/notification-policies';
|
||||
import { createUrl } from '../../utils/url';
|
||||
import { HoverCard } from '../HoverCard';
|
||||
import { Label } from '../Label';
|
||||
import { MetaText } from '../MetaText';
|
||||
@ -72,6 +75,7 @@ const Policy: FC<PolicyComponentProps> = ({
|
||||
const permissions = getNotificationsPermissions(alertManagerSourceName);
|
||||
const canEditRoutes = contextSrv.hasPermission(permissions.update);
|
||||
const canDeleteRoutes = contextSrv.hasPermission(permissions.delete);
|
||||
const canReadProvisioning = contextSrv.hasAccess(permissions.provisioning.read, isOrgAdmin());
|
||||
|
||||
const contactPoint = currentRoute.receiver;
|
||||
const continueMatching = currentRoute.continue ?? false;
|
||||
@ -122,6 +126,9 @@ const Policy: FC<PolicyComponentProps> = ({
|
||||
? sumBy(matchingAlertGroups, (group) => group.alerts.length)
|
||||
: undefined;
|
||||
|
||||
const isGrafanaAM = alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME;
|
||||
const showExport = isGrafanaAM && isDefaultPolicy && canReadProvisioning;
|
||||
|
||||
// TODO dead branch detection, warnings for all sort of configs that won't work or will never be activated
|
||||
return (
|
||||
<Stack direction="column" gap={1.5}>
|
||||
@ -148,56 +155,73 @@ const Policy: FC<PolicyComponentProps> = ({
|
||||
{/* TODO maybe we should move errors to the gutter instead? */}
|
||||
{errors.length > 0 && <Errors errors={errors} />}
|
||||
{provisioned && <ProvisioningBadge />}
|
||||
{readOnly ? null : (
|
||||
{readOnly && !showExport ? null : (
|
||||
<Stack direction="row" gap={0.5}>
|
||||
<ConditionalWrap shouldWrap={provisioned} wrap={ProvisionedTooltip}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
size="sm"
|
||||
onClick={() => onAddPolicy(currentRoute)}
|
||||
disabled={provisioned}
|
||||
type="button"
|
||||
>
|
||||
New nested policy
|
||||
</Button>
|
||||
</ConditionalWrap>
|
||||
{!readOnly && (
|
||||
<ConditionalWrap shouldWrap={provisioned} wrap={ProvisionedTooltip}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
size="sm"
|
||||
onClick={() => onAddPolicy(currentRoute)}
|
||||
disabled={provisioned}
|
||||
type="button"
|
||||
>
|
||||
New nested policy
|
||||
</Button>
|
||||
</ConditionalWrap>
|
||||
)}
|
||||
|
||||
<ConditionalWrap shouldWrap={provisioned} wrap={ProvisionedTooltip}>
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
{!readOnly && (
|
||||
<ConditionalWrap shouldWrap={provisioned} wrap={ProvisionedTooltip}>
|
||||
<Menu.Item
|
||||
icon="edit"
|
||||
disabled={!isEditable || provisioned}
|
||||
label="Edit"
|
||||
onClick={() => onEditPolicy(currentRoute, isDefaultPolicy)}
|
||||
/>
|
||||
</ConditionalWrap>
|
||||
)}
|
||||
{showExport && (
|
||||
<Menu.Item
|
||||
icon="edit"
|
||||
disabled={!isEditable}
|
||||
label="Edit"
|
||||
onClick={() => onEditPolicy(currentRoute, isDefaultPolicy)}
|
||||
icon="download-alt"
|
||||
label="Export"
|
||||
url={createUrl('/api/v1/provisioning/policies/export', {
|
||||
download: 'true',
|
||||
format: 'yaml',
|
||||
})}
|
||||
target="_blank"
|
||||
/>
|
||||
{isDeletable && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
)}
|
||||
{!readOnly && isDeletable && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<ConditionalWrap shouldWrap={provisioned} wrap={ProvisionedTooltip}>
|
||||
<Menu.Item
|
||||
destructive
|
||||
icon="trash-alt"
|
||||
disabled={!isDeletable || provisioned}
|
||||
label="Delete"
|
||||
onClick={() => onDeletePolicy(currentRoute)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
icon="ellipsis-h"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
type="button"
|
||||
aria-label="more-actions"
|
||||
data-testid="more-actions"
|
||||
disabled={provisioned}
|
||||
/>
|
||||
</Dropdown>
|
||||
</ConditionalWrap>
|
||||
</ConditionalWrap>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
icon="ellipsis-h"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
type="button"
|
||||
aria-label="more-actions"
|
||||
data-testid="more-actions"
|
||||
/>
|
||||
</Dropdown>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
Loading…
Reference in New Issue
Block a user