mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Fix fine-grained rule access control to use 403 for authorization error (#79239)
* use 403 for authorization error * update silences API * add ForbiddenError to rule API responses
This commit is contained in:
parent
aa12c6c772
commit
2be7605794
pkg
services/ngalert
accesscontrol
api
tests/api/alerting
public
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errAuthorizationGeneric = errutil.Unauthorized("alerting.unauthorized")
|
||||
errAuthorizationGeneric = errutil.Forbidden("alerting.unauthorized")
|
||||
)
|
||||
|
||||
func NewAuthorizationErrorWithPermissions(action string, eval accesscontrol.Evaluator) error {
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@ -67,12 +68,13 @@ func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postab
|
||||
if postableSilence.ID == "" {
|
||||
action = accesscontrol.ActionAlertingInstanceCreate
|
||||
}
|
||||
if !accesscontrol.HasAccess(srv.ac, c)(accesscontrol.EvalPermission(action)) {
|
||||
evaluator := accesscontrol.EvalPermission(action)
|
||||
if !accesscontrol.HasAccess(srv.ac, c)(evaluator) {
|
||||
errAction := "update"
|
||||
if postableSilence.ID == "" {
|
||||
errAction = "create"
|
||||
}
|
||||
return ErrResp(http.StatusUnauthorized, fmt.Errorf("user is not authorized to %s silences", errAction), "")
|
||||
return response.Err(authz.NewAuthorizationErrorWithPermissions(fmt.Sprintf("%s silences", errAction), evaluator))
|
||||
}
|
||||
|
||||
silenceID, err := am.CreateSilence(c.Req.Context(), &postableSilence)
|
||||
|
@ -571,7 +571,7 @@ func TestRouteCreateSilence(t *testing.T) {
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {},
|
||||
},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "new silence, role-based access control is enabled, authorized",
|
||||
@ -587,7 +587,7 @@ func TestRouteCreateSilence(t *testing.T) {
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is enabled, authorized",
|
||||
|
@ -50,7 +50,7 @@ var (
|
||||
|
||||
// RouteDeleteAlertRules deletes all alert rules the user is authorized to access in the given namespace
|
||||
// or, if non-empty, a specific group of rules in the namespace.
|
||||
// Returns http.StatusUnauthorized if user does not have access to any of the rules that match the filter.
|
||||
// Returns http.StatusForbidden if user does not have access to any of the rules that match the filter.
|
||||
// Returns http.StatusBadRequest if all rules that match the filter and the user is authorized to delete are provisioned.
|
||||
func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response {
|
||||
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
@ -170,7 +170,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam
|
||||
}
|
||||
|
||||
// RouteGetRulesGroupConfig returns rules that belong to a specific group in a specific namespace (folder).
|
||||
// If user does not have access to at least one of the rule in the group, returns status 401 Unauthorized
|
||||
// If user does not have access to at least one of the rule in the group, returns status 403 Forbidden
|
||||
func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespaceTitle string, ruleGroup string) response.Response {
|
||||
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
if err != nil {
|
||||
|
@ -352,27 +352,27 @@ func TestExportRules(t *testing.T) {
|
||||
expectedStatus: 400,
|
||||
},
|
||||
{
|
||||
title: "unauthorized if folders are not accessible",
|
||||
title: "forbidden if folders are not accessible",
|
||||
params: url.Values{
|
||||
"folderUid": []string{noAccessByFolder[0].NamespaceUID},
|
||||
},
|
||||
expectedStatus: 401,
|
||||
expectedStatus: http.StatusForbidden,
|
||||
expectedRules: nil,
|
||||
},
|
||||
{
|
||||
title: "unauthorized if group is not accessible",
|
||||
title: "forbidden if group is not accessible",
|
||||
params: url.Values{
|
||||
"folderUid": []string{noAccessKey1.NamespaceUID},
|
||||
"group": []string{noAccessKey1.RuleGroup},
|
||||
},
|
||||
expectedStatus: 401,
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
title: "unauthorized if rule's group is not accessible",
|
||||
title: "forbidden if rule's group is not accessible",
|
||||
params: url.Values{
|
||||
"ruleUid": []string{noAccessRule.UID},
|
||||
},
|
||||
expectedStatus: 401,
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
title: "return in JSON if header is specified",
|
||||
|
@ -73,14 +73,14 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
|
||||
t.Run("when fine-grained access is enabled", func(t *testing.T) {
|
||||
t.Run("and group argument is empty", func(t *testing.T) {
|
||||
t.Run("return 401 if user is not authorized to access any group in the folder", func(t *testing.T) {
|
||||
t.Run("return Forbidden if user is not authorized to access any group in the folder", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
|
||||
request := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil)
|
||||
|
||||
response := createService(ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
require.Equalf(t, http.StatusForbidden, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))
|
||||
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
@ -139,7 +139,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
})
|
||||
t.Run("and group argument is not empty", func(t *testing.T) {
|
||||
groupName := util.GenerateShortUID()
|
||||
t.Run("return 401 if user is not authorized to access the group", func(t *testing.T) {
|
||||
t.Run("return Forbidden if user is not authorized to access the group", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
|
||||
authorizedRulesInGroup := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
|
||||
@ -152,7 +152,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
|
||||
response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
require.Equalf(t, http.StatusForbidden, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))
|
||||
deleteCommands := getRecordedCommand(ruleStore)
|
||||
require.Empty(t, deleteCommands)
|
||||
})
|
||||
@ -396,14 +396,14 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey)))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
|
||||
t.Run("and return 401 if user does not have access one of rules", func(t *testing.T) {
|
||||
t.Run("and return Forbidden if user does not have access one of rules", func(t *testing.T) {
|
||||
permissions := createPermissionsForRules(expectedRules[1:], orgID)
|
||||
request := createRequestContextWithPerms(orgID, permissions, map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
":Groupname": groupKey.RuleGroup,
|
||||
})
|
||||
response := createService(ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
require.Equal(t, http.StatusForbidden, response.Status())
|
||||
})
|
||||
|
||||
t.Run("and return rules if user has access to all of them", func(t *testing.T) {
|
||||
|
@ -137,7 +137,7 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should return 401 if user cannot query a data source", func(t *testing.T) {
|
||||
t.Run("should return Forbidden if user cannot query a data source", func(t *testing.T) {
|
||||
data1 := models.GenerateAlertQuery()
|
||||
data2 := models.GenerateAlertQuery()
|
||||
|
||||
@ -156,7 +156,7 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
NamespaceTitle: "test-folder",
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
require.Equal(t, http.StatusForbidden, response.Status())
|
||||
})
|
||||
|
||||
t.Run("should return 200 if user can query all data sources", func(t *testing.T) {
|
||||
@ -208,7 +208,7 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should return 401 if user cannot query a data source", func(t *testing.T) {
|
||||
t.Run("should return Forbidden if user cannot query a data source", func(t *testing.T) {
|
||||
data1 := models.GenerateAlertQuery()
|
||||
data2 := models.GenerateAlertQuery()
|
||||
|
||||
@ -224,7 +224,7 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
Now: time.Time{},
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
require.Equal(t, http.StatusForbidden, response.Status())
|
||||
})
|
||||
|
||||
t.Run("should return 200 if user can query all data sources", func(t *testing.T) {
|
||||
|
@ -280,9 +280,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AlertStateType": {
|
||||
"type": "string"
|
||||
},
|
||||
"AlertingFileExport": {
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
@ -404,80 +401,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Annotation": {
|
||||
"properties": {
|
||||
"alertId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"alertName": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"dashboardId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"dashboardUID": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/definitions/Json"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"login": {
|
||||
"type": "string"
|
||||
},
|
||||
"newState": {
|
||||
"type": "string"
|
||||
},
|
||||
"panelId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"prevState": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"time": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"timeEnd": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"updated": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"userId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiRuleNode": {
|
||||
"properties": {
|
||||
"alert": {
|
||||
@ -657,75 +580,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"CookieType": {
|
||||
"type": "string"
|
||||
},
|
||||
"CounterResetHint": {
|
||||
"description": "or alternatively that we are dealing with a gauge histogram, where counter resets do not apply.",
|
||||
"format": "uint8",
|
||||
"title": "CounterResetHint contains the known information about a counter reset,",
|
||||
"type": "integer"
|
||||
},
|
||||
"CreateLibraryElementCommand": {
|
||||
"description": "CreateLibraryElementCommand is the command for adding a LibraryElement",
|
||||
"properties": {
|
||||
"folderId": {
|
||||
"description": "ID of the folder where the library element is stored.\n\nDeprecated: use FolderUID instead",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"folderUid": {
|
||||
"description": "UID of the folder where the library element is stored.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind of element to create, Use 1 for library panels or 2 for c.\nDescription:\n1 - library panels\n2 - library variables",
|
||||
"enum": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"model": {
|
||||
"description": "The JSON model for the library element.",
|
||||
"type": "object"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the library element.",
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"DashboardACLUpdateItem": {
|
||||
"properties": {
|
||||
"permission": {
|
||||
"$ref": "#/definitions/PermissionType"
|
||||
},
|
||||
"role": {
|
||||
"enum": [
|
||||
"None",
|
||||
"Viewer",
|
||||
"Editor",
|
||||
"Admin"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"teamId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"userId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"DataLink": {
|
||||
"description": "DataLink define what",
|
||||
"properties": {
|
||||
@ -988,6 +848,9 @@
|
||||
},
|
||||
"EvalQueriesPayload": {
|
||||
"properties": {
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertQuery"
|
||||
@ -1195,6 +1058,14 @@
|
||||
"title": "FloatHistogram is similar to Histogram but uses float64 for all\ncounts. Additionally, bucket counts are absolute and not deltas.",
|
||||
"type": "object"
|
||||
},
|
||||
"ForbiddenError": {
|
||||
"properties": {
|
||||
"body": {
|
||||
"$ref": "#/definitions/PublicError"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Frame": {
|
||||
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
|
||||
"properties": {
|
||||
@ -1960,82 +1831,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"LegacyAlert": {
|
||||
"properties": {
|
||||
"Created": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"DashboardID": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"EvalData": {
|
||||
"$ref": "#/definitions/Json"
|
||||
},
|
||||
"ExecutionError": {
|
||||
"type": "string"
|
||||
},
|
||||
"For": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"Frequency": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"Handler": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"ID": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"Message": {
|
||||
"type": "string"
|
||||
},
|
||||
"Name": {
|
||||
"type": "string"
|
||||
},
|
||||
"NewStateDate": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"OrgID": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"PanelID": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"Settings": {
|
||||
"$ref": "#/definitions/Json"
|
||||
},
|
||||
"Severity": {
|
||||
"type": "string"
|
||||
},
|
||||
"Silenced": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"State": {
|
||||
"$ref": "#/definitions/AlertStateType"
|
||||
},
|
||||
"StateChanges": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"Updated": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"Version": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"LinkTransformationConfig": {
|
||||
"properties": {
|
||||
"expression": {
|
||||
@ -2107,48 +1902,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"MetricRequest": {
|
||||
"properties": {
|
||||
"debug": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"from": {
|
||||
"description": "From Start time in epoch timestamps in milliseconds or relative using Grafana time units.",
|
||||
"example": "now-1h",
|
||||
"type": "string"
|
||||
},
|
||||
"queries": {
|
||||
"description": "queries.refId – Specifies an identifier of the query. Is optional and default to “A”.\nqueries.datasourceId – Specifies the data source to be queried. Each query in the request must have an unique datasourceId.\nqueries.maxDataPoints - Species maximum amount of data points that dashboard panel can render. Is optional and default to 100.\nqueries.intervalMs - Specifies the time interval in milliseconds of time series. Is optional and defaults to 1000.",
|
||||
"example": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"format": "table",
|
||||
"intervalMs": 86400000,
|
||||
"maxDataPoints": 1092,
|
||||
"rawSql": "SELECT 1 as valueOne, 2 as valueTwo",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Json"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"to": {
|
||||
"description": "To End time in epoch timestamps in milliseconds or relative using Grafana time units.",
|
||||
"example": "now",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"from",
|
||||
"to",
|
||||
"queries"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MultiStatus": {
|
||||
"type": "object"
|
||||
},
|
||||
@ -2182,24 +1935,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NewApiKeyResult": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"example": 1,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"example": "glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"example": "grafana",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NotFound": {
|
||||
"type": "object"
|
||||
},
|
||||
@ -2230,12 +1965,58 @@
|
||||
},
|
||||
"NotificationPolicyExport": {
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
"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"
|
||||
},
|
||||
"orgId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
@ -2504,56 +2285,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PatchPrefsCmd": {
|
||||
"properties": {
|
||||
"cookies": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CookieType"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"homeDashboardId": {
|
||||
"default": 0,
|
||||
"description": "The numerical :id of a favorited dashboard",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"homeDashboardUID": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
"queryHistory": {
|
||||
"$ref": "#/definitions/QueryHistoryPreference"
|
||||
},
|
||||
"theme": {
|
||||
"enum": [
|
||||
"light",
|
||||
"dark"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"timezone": {
|
||||
"enum": [
|
||||
"utc",
|
||||
"browser"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"weekStart": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PermissionDenied": {
|
||||
"type": "object"
|
||||
},
|
||||
"PermissionType": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"Point": {
|
||||
"description": "If H is not nil, then this is a histogram point and only (T, H) is valid.\nIf H is nil, then only (T, V) is valid.",
|
||||
"properties": {
|
||||
@ -3057,6 +2791,26 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PublicError": {
|
||||
"description": "PublicError is derived from Error and only contains information\navailable to the end user.",
|
||||
"properties": {
|
||||
"extra": {
|
||||
"additionalProperties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCode": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PushoverConfig": {
|
||||
"properties": {
|
||||
"device": {
|
||||
@ -3113,14 +2867,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"QueryHistoryPreference": {
|
||||
"properties": {
|
||||
"homeTab": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"QueryStat": {
|
||||
"description": "The embedded FieldConfig's display name must be set.\nIt corresponds to the QueryResultMetaStat on the frontend (https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L53).",
|
||||
"properties": {
|
||||
@ -4218,7 +3964,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the EscapedPath method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@ -4254,62 +3999,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateDashboardACLCommand": {
|
||||
"properties": {
|
||||
"items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/DashboardACLUpdateItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UpdatePrefsCmd": {
|
||||
"properties": {
|
||||
"cookies": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CookieType"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"homeDashboardId": {
|
||||
"default": 0,
|
||||
"description": "The numerical :id of a favorited dashboard",
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"homeDashboardUID": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
"queryHistory": {
|
||||
"$ref": "#/definitions/QueryHistoryPreference"
|
||||
},
|
||||
"theme": {
|
||||
"enum": [
|
||||
"light",
|
||||
"dark",
|
||||
"system"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"timezone": {
|
||||
"enum": [
|
||||
"utc",
|
||||
"browser"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"weekStart": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateRuleGroupResponse": {
|
||||
@ -4515,7 +4205,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"properties": {
|
||||
"alerts": {
|
||||
"description": "alerts",
|
||||
@ -4644,7 +4333,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labelSet"
|
||||
@ -4707,6 +4395,7 @@
|
||||
"type": "array"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -4755,6 +4444,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
@ -4905,7 +4595,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -4943,6 +4632,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"properties": {
|
||||
"active": {
|
||||
"description": "active",
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: NamespaceConfigResponse
|
||||
// 403: ForbiddenError
|
||||
//
|
||||
|
||||
// swagger:route Get /api/ruler/grafana/api/v1/export/rules ruler RouteGetRulesForExport
|
||||
@ -29,6 +30,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertingFileExport
|
||||
// 403: ForbiddenError
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route Get /api/ruler/{DatasourceUID}/api/v1/rules ruler RouteGetRulesConfig
|
||||
@ -40,6 +42,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: NamespaceConfigResponse
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:route POST /api/ruler/grafana/api/v1/rules/{Namespace} ruler RoutePostNameGrafanaRulesConfig
|
||||
@ -52,6 +55,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: UpdateRuleGroupResponse
|
||||
// 403: ForbiddenError
|
||||
//
|
||||
|
||||
// swagger:route POST /api/ruler/grafana/api/v1/rules/{Namespace}/export ruler RoutePostRulesGroupForExport
|
||||
@ -64,6 +68,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertingFileExport
|
||||
// 403: ForbiddenError
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route POST /api/ruler/{DatasourceUID}/api/v1/rules/{Namespace} ruler RoutePostNameRulesConfig
|
||||
@ -76,6 +81,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:route Get /api/ruler/grafana/api/v1/rules/{Namespace} ruler RouteGetNamespaceGrafanaRulesConfig
|
||||
@ -86,6 +92,7 @@ import (
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 403: ForbiddenError
|
||||
// 202: NamespaceConfigResponse
|
||||
|
||||
// swagger:route Get /api/ruler/{DatasourceUID}/api/v1/rules/{Namespace} ruler RouteGetNamespaceRulesConfig
|
||||
@ -97,6 +104,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: NamespaceConfigResponse
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:route Delete /api/ruler/grafana/api/v1/rules/{Namespace} ruler RouteDeleteNamespaceGrafanaRulesConfig
|
||||
@ -105,6 +113,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
// 403: ForbiddenError
|
||||
|
||||
// swagger:route Delete /api/ruler/{DatasourceUID}/api/v1/rules/{Namespace} ruler RouteDeleteNamespaceRulesConfig
|
||||
//
|
||||
@ -112,6 +121,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:route Get /api/ruler/grafana/api/v1/rules/{Namespace}/{Groupname} ruler RouteGetGrafanaRuleGroupConfig
|
||||
@ -123,6 +133,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: RuleGroupConfigResponse
|
||||
// 403: ForbiddenError
|
||||
|
||||
// swagger:route Get /api/ruler/{DatasourceUID}/api/v1/rules/{Namespace}/{Groupname} ruler RouteGetRulegGroupConfig
|
||||
//
|
||||
@ -133,6 +144,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: RuleGroupConfigResponse
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:route Delete /api/ruler/grafana/api/v1/rules/{Namespace}/{Groupname} ruler RouteDeleteGrafanaRuleGroupConfig
|
||||
@ -141,6 +153,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
// 403: ForbiddenError
|
||||
|
||||
// swagger:route Delete /api/ruler/{DatasourceUID}/api/v1/rules/{Namespace}/{Groupname} ruler RouteDeleteRuleGroupConfig
|
||||
//
|
||||
@ -148,6 +161,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:parameters RoutePostNameRulesConfig RoutePostNameGrafanaRulesConfig RoutePostRulesGroupForExport
|
||||
|
@ -1,5 +1,7 @@
|
||||
package definitions
|
||||
|
||||
import "github.com/grafana/grafana/pkg/util/errutil"
|
||||
|
||||
// swagger:model
|
||||
type NotFound struct{}
|
||||
|
||||
@ -11,3 +13,10 @@ type ValidationError struct {
|
||||
// example: error message
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type ForbiddenError struct {
|
||||
// The response message
|
||||
// in: body
|
||||
Body errutil.PublicError `json:"body"`
|
||||
}
|
||||
|
@ -848,6 +848,9 @@
|
||||
},
|
||||
"EvalQueriesPayload": {
|
||||
"properties": {
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertQuery"
|
||||
@ -1055,6 +1058,14 @@
|
||||
"title": "FloatHistogram is similar to Histogram but uses float64 for all\ncounts. Additionally, bucket counts are absolute and not deltas.",
|
||||
"type": "object"
|
||||
},
|
||||
"ForbiddenError": {
|
||||
"properties": {
|
||||
"body": {
|
||||
"$ref": "#/definitions/PublicError"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Frame": {
|
||||
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
|
||||
"properties": {
|
||||
@ -1954,12 +1965,58 @@
|
||||
},
|
||||
"NotificationPolicyExport": {
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
"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"
|
||||
},
|
||||
"orgId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
@ -2734,6 +2791,26 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PublicError": {
|
||||
"description": "PublicError is derived from Error and only contains information\navailable to the end user.",
|
||||
"properties": {
|
||||
"extra": {
|
||||
"additionalProperties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCode": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PushoverConfig": {
|
||||
"properties": {
|
||||
"device": {
|
||||
@ -3887,7 +3964,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the EscapedPath method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@ -3923,7 +3999,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateRuleGroupResponse": {
|
||||
@ -4129,7 +4205,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"properties": {
|
||||
"alerts": {
|
||||
"description": "alerts",
|
||||
@ -4314,13 +4389,13 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -4520,7 +4595,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -5910,6 +5984,12 @@
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
@ -5945,6 +6025,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
@ -5970,6 +6056,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
@ -5996,6 +6088,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
@ -6030,6 +6128,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/UpdateRuleGroupResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
@ -6081,6 +6185,12 @@
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
@ -6114,6 +6224,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
@ -6146,6 +6262,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RuleGroupConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
@ -6187,6 +6309,12 @@
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -6225,6 +6353,12 @@
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -6264,6 +6398,12 @@
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -6311,6 +6451,12 @@
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -6355,6 +6501,12 @@
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -6400,6 +6552,12 @@
|
||||
"$ref": "#/definitions/RuleGroupConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
|
@ -1252,6 +1252,12 @@
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
@ -1287,6 +1293,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1315,6 +1327,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1349,6 +1367,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/UpdateRuleGroupResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1372,6 +1396,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1423,6 +1453,12 @@
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
@ -1459,6 +1495,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/RuleGroupConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1488,6 +1530,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1529,6 +1577,12 @@
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -1570,6 +1624,12 @@
|
||||
"$ref": "#/definitions/NamespaceConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -1617,6 +1677,12 @@
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -1653,6 +1719,12 @@
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -1700,6 +1772,12 @@
|
||||
"$ref": "#/definitions/RuleGroupConfigResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -1742,6 +1820,12 @@
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
@ -3828,6 +3912,9 @@
|
||||
"EvalQueriesPayload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -4036,6 +4123,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ForbiddenError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"$ref": "#/definitions/PublicError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Frame": {
|
||||
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
|
||||
"type": "object",
|
||||
@ -4938,12 +5033,58 @@
|
||||
"type": "object",
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
"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"
|
||||
},
|
||||
"orgId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -5716,6 +5857,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"PublicError": {
|
||||
"description": "PublicError is derived from Error and only contains information\navailable to the end user.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extra": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PushoverConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -6869,9 +7030,8 @@
|
||||
}
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the EscapedPath method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"type": "object",
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@ -7111,7 +7271,6 @@
|
||||
}
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"alerts",
|
||||
@ -7299,6 +7458,7 @@
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
@ -7306,7 +7466,6 @@
|
||||
"$ref": "#/definitions/gettableAlerts"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@ -7509,7 +7668,6 @@
|
||||
}
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
|
@ -1049,7 +1049,7 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
},
|
||||
expectedCode: func() int {
|
||||
if setting.IsEnterprise {
|
||||
return http.StatusUnauthorized
|
||||
return http.StatusForbidden
|
||||
}
|
||||
return http.StatusBadRequest
|
||||
}(),
|
||||
@ -2285,7 +2285,7 @@ func TestIntegrationEval(t *testing.T) {
|
||||
expectedResponse: func() string { return "" },
|
||||
expectedStatusCode: func() int {
|
||||
if setting.IsEnterprise {
|
||||
return http.StatusUnauthorized
|
||||
return http.StatusForbidden
|
||||
}
|
||||
return http.StatusBadRequest
|
||||
},
|
||||
|
@ -124,7 +124,7 @@ func TestBacktesting(t *testing.T) {
|
||||
t.Run("fail if can't query data sources", func(t *testing.T) {
|
||||
status, body := testUserApiCli.SubmitRuleForBacktesting(t, queryRequest)
|
||||
require.Contains(t, body, "user is not authorized to access rule group")
|
||||
require.Equalf(t, http.StatusUnauthorized, status, "Response: %s", body)
|
||||
require.Equalf(t, http.StatusForbidden, status, "Response: %s", body)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder2"},
|
||||
})
|
||||
assert.Equal(t, http.StatusUnauthorized, status)
|
||||
assert.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("Export from one group", func(t *testing.T) {
|
||||
|
@ -13855,6 +13855,9 @@
|
||||
"EvalQueriesPayload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -14198,6 +14201,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ForbiddenError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"$ref": "#/definitions/PublicError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Frame": {
|
||||
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
|
||||
"type": "object",
|
||||
@ -15786,12 +15797,58 @@
|
||||
"type": "object",
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
"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"
|
||||
},
|
||||
"orgId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/RouteExport"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -17189,6 +17246,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"PublicError": {
|
||||
"description": "PublicError is derived from Error and only contains information\navailable to the end user.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extra": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PublicKeyAlgorithm": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
@ -20459,7 +20536,6 @@
|
||||
}
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"alerts",
|
||||
@ -20616,7 +20692,6 @@
|
||||
}
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"labels",
|
||||
@ -20679,6 +20754,7 @@
|
||||
}
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@ -20727,6 +20803,7 @@
|
||||
}
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
@ -20877,7 +20954,6 @@
|
||||
}
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@ -20943,6 +21019,7 @@
|
||||
}
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"active",
|
||||
|
@ -4872,6 +4872,9 @@
|
||||
},
|
||||
"EvalQueriesPayload": {
|
||||
"properties": {
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AlertQuery"
|
||||
@ -5216,6 +5219,14 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ForbiddenError": {
|
||||
"properties": {
|
||||
"body": {
|
||||
"$ref": "#/components/schemas/PublicError"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Frame": {
|
||||
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
|
||||
"properties": {
|
||||
@ -6802,12 +6813,58 @@
|
||||
},
|
||||
"NotificationPolicyExport": {
|
||||
"properties": {
|
||||
"Policy": {
|
||||
"$ref": "#/components/schemas/RouteExport"
|
||||
"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": "#/components/schemas/MatchRegexps"
|
||||
},
|
||||
"matchers": {
|
||||
"$ref": "#/components/schemas/Matchers"
|
||||
},
|
||||
"mute_time_intervals": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/components/schemas/ObjectMatchers"
|
||||
},
|
||||
"orgId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
"repeat_interval": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/RouteExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "NotificationPolicyExport is the provisioned file export of alerting.NotificiationPolicyV1.",
|
||||
@ -8206,6 +8263,26 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PublicError": {
|
||||
"description": "PublicError is derived from Error and only contains information\navailable to the end user.",
|
||||
"properties": {
|
||||
"extra": {
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCode": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PublicKeyAlgorithm": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
@ -11475,7 +11552,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"properties": {
|
||||
"alerts": {
|
||||
"description": "alerts",
|
||||
@ -11632,7 +11708,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"$ref": "#/components/schemas/labelSet"
|
||||
@ -11695,6 +11770,7 @@
|
||||
"type": "array"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -11743,6 +11819,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/gettableSilence"
|
||||
},
|
||||
@ -11893,7 +11970,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -11959,6 +12035,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"properties": {
|
||||
"active": {
|
||||
"description": "active",
|
||||
|
Loading…
Reference in New Issue
Block a user