query_builder for more modular wazuh-indexer searches (#58)
taylor_socfortress committed
Jul 24, 2023 at 13:41 UTC
2ed4d4351674b17a5d1f9e520cd9760ddeaff9fc
4 files changed
+334
-101
backend/app/routes/alerts.py
+15
-23
@@ -41,25 +41,9 @@ def get_alerts_by_agent(agent_name: str) -> jsonify:
41
containing all its associated data.
42
"""
43
service = AlertsService()
44
- alerts = service.collect_alerts_by_agent_name(agent_name=agent_name)
45
- return jsonify(alerts)
46
-
47
-
48
-@bp.route("/alerts/top_10", methods=["GET"])
49
-def get_top_10_alerts() -> jsonify:
50
- """
51
- Retrieves top 10 alerts from the AlertsService per index.
52
-
53
- This endpoint retrieves top 10 alerts from the AlertsService. It does this by creating an instance of
54
- the AlertsService class and calling its `collect_alerts` method. The result is a list of top 10 alerts currently
55
- available.
56
-
57
- Returns:
58
- jsonify: A JSON response containing a list of alerts. Each item in the list is a dictionary representing an alert,
59
- containing all its associated data.
60
- """
61
- service = AlertsService()
62
- alerts = service.collect_alerts(size=10) # replace `collect_top_10_alerts` with `collect_alerts(size=10)`
44
+ size = request.args.get("size", default=10, type=int)
45
+ timerange = request.args.get("timerange", default="24h", type=str)
46
+ alerts = service.collect_alerts_by_agent_name(agent_name=agent_name, size=size, timerange=timerange)
47
return jsonify(alerts)
48
49
@@ -76,8 +60,10 @@ def get_alerts_by_index(index_name: str) -> jsonify:
60
jsonify: A JSON response containing a list of alerts. Each item in the list is a dictionary representing an alert,
61
containing all its associated data.
62
"""
63
+ size = request.args.get("size", default=10, type=int)
64
+ timerange = request.args.get("timerange", default="24h", type=str)
65
service = AlertsService()
80
- alerts = service.collect_alerts_by_index(index_name=index_name, size=1000)
66
+ alerts = service.collect_alerts_by_index(index_name=index_name, size=size, timerange=timerange)
67
return jsonify(alerts)
68
69
@@ -94,8 +80,10 @@ def get_hosts() -> jsonify:
80
jsonify: A JSON response containing a list of hosts. Each item in the list is a dictionary representing a host,
81
containing all its associated data.
82
"""
83
+ size = request.args.get("size", default=10, type=int)
84
+ timerange = request.args.get("timerange", default="24h", type=str)
85
service = AlertsService()
98
- hosts = service.collect_alerts_by_host()
86
+ hosts = service.collect_alerts_by_host(size=size, timerange=timerange)
87
return jsonify(hosts)
88
89
@@ -112,8 +100,10 @@ def get_rules() -> jsonify:
100
jsonify: A JSON response containing a list of rules. Each item in the list is a dictionary representing a rule,
101
containing all its associated data.
102
"""
103
+ size = request.args.get("size", default=10, type=int)
104
+ timerange = request.args.get("timerange", default="24h", type=str)
105
service = AlertsService()
116
- rules = service.collect_alerts_by_rule()
106
+ rules = service.collect_alerts_by_rule(size=size, timerange=timerange)
107
return jsonify(rules)
108
109
@@ -130,8 +120,10 @@ def get_rules_by_host() -> jsonify:
120
jsonify: A JSON response containing a list of rules. Each item in the list is a dictionary representing a rule,
121
containing all its associated data.
122
"""
123
+ size = request.args.get("size", default=10, type=int)
124
+ timerange = request.args.get("timerange", default="24h", type=str)
125
service = AlertsService()
134
- rules = service.collect_alerts_by_rule_per_host()
126
+ rules = service.collect_alerts_by_rule_per_host(size=size, timerange=timerange)
127
return jsonify(rules)
128
129
backend/app/services/WazuhIndexer/alerts.py
+127
-37
@@ -1,13 +1,14 @@
1
-from datetime import datetime
2
-from datetime import timedelta
1
from typing import Any
2
from typing import Dict
3
+from typing import Iterable
4
+from typing import Tuple
5
6
from elasticsearch7 import Elasticsearch
7
from loguru import logger
8
9
from app.services.ask_socfortress.universal import AskSocfortressService
10
from app.services.DFIR_IRIS.alerts import IRISAlertsService
11
+from app.services.WazuhIndexer.universal import QueryBuilder
12
from app.services.WazuhIndexer.universal import UniversalService
13
14
@@ -88,8 +89,10 @@ class AlertsService:
89
return indices_validation
90
91
alerts_summary = []
92
+ # matches = [("syslog_level", "ALERT"), ("agent_name", "WIN-39O01J5F7G5")]
93
+ matches = [("syslog_level", "ALERT")]
94
for index_name in indices_validation["indices"]:
92
- alerts = self._collect_alerts(index_name, size=size, timerange=timerange)
95
+ alerts = self._collect_alerts(index_name, size=size, timerange=timerange, matches=matches)
96
if alerts["success"] and len(alerts["alerts"]) > 0:
97
alerts_summary.append(
98
{
@@ -129,14 +132,22 @@ class AlertsService:
132
"alerts_summary": alerts_summary,
133
}
134
132
- def collect_alerts_by_index(self, index_name: str, size: int) -> Dict[str, Any]:
135
+ def collect_alerts_by_index(self, index_name: str, size: int, timerange: str) -> Dict[str, Any]:
136
"""
137
Collects alerts from the given index.
138
+
139
+ Args:
140
+ index_name (str): The name of the index to query.
141
+ size (int): The maximum number of alerts to return.
142
+ timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
143
+
144
+ Returns:
145
+ Dict[str, Any]: A dictionary containing success status, a message, and potentially the alerts from the given index.
146
"""
147
if not self.is_valid_index(index_name):
148
return self._error_response("Invalid index name")
149
139
- alerts = self._collect_alerts(index_name=index_name, size=size)
150
+ alerts = self._collect_alerts(index_name=index_name, size=size, timerange=timerange)
151
if not alerts["success"]:
152
return alerts
153
@@ -147,12 +158,14 @@ class AlertsService:
158
"total_alerts": len(alerts["alerts"]),
159
}
160
150
- def collect_alerts_by_agent_name(self, agent_name: str) -> Dict[str, Any]:
161
+ def collect_alerts_by_agent_name(self, agent_name: str, size: int, timerange: str) -> Dict[str, Any]:
162
"""
163
Collects alerts associated with a given agent name.
164
165
Args:
166
agent_name (str): The agent name associated with the alerts.
167
+ size (int): The maximum number of alerts to return.
168
+ timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
169
170
Returns:
171
Dict[str, Any]: A dictionary containing success status, a message, and potentially the alerts associated with the agent.
@@ -162,8 +175,9 @@ class AlertsService:
175
return indices_validation
176
177
alerts_by_agent_dict = {}
178
+ matches = [("syslog_level", "ALERT"), ("agent_name", f"{agent_name}")]
179
for index_name in indices_validation["indices"]:
166
- alerts = self._collect_alerts(index_name=index_name, size=1000)
180
+ alerts = self._collect_alerts(index_name=index_name, size=size, timerange=timerange, matches=matches)
181
if alerts["success"]:
182
for alert in alerts["alerts"]:
183
if alert["_source"]["agent_name"] == agent_name:
@@ -177,9 +191,16 @@ class AlertsService:
191
"alerts_by_agent": alerts_by_agent_list,
192
}
193
180
- def collect_alerts_by_host(self) -> Dict[str, int]:
194
+ def collect_alerts_by_host(self, size: int, timerange: str) -> Dict[str, int]:
195
"""
196
Collects the number of alerts per host.
197
+
198
+ Args:
199
+ size (int): The maximum number of alerts to return.
200
+ timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
201
+
202
+ Returns:
203
+ Dict[str, int]: A dictionary containing success status and the number of alerts per host or an error message.
204
"""
205
indices_validation = self._collect_indices_and_validate()
206
if not indices_validation["success"]:
@@ -187,7 +208,7 @@ class AlertsService:
208
209
alerts_by_host_dict = {}
210
for index_name in indices_validation["indices"]:
190
- alerts = self._collect_alerts(index_name=index_name, size=1000)
211
+ alerts = self._collect_alerts(index_name=index_name, size=size, timerange=timerange)
212
if alerts["success"]:
213
for alert in alerts["alerts"]:
214
host = alert["_source"]["agent_name"]
@@ -201,9 +222,16 @@ class AlertsService:
222
"alerts_by_host": alerts_by_host_list,
223
}
224
204
- def collect_alerts_by_rule(self) -> Dict[str, int]:
225
+ def collect_alerts_by_rule(self, size: int, timerange: str) -> Dict[str, int]:
226
"""
227
Collects the number of alerts per rule.
228
+
229
+ Args:
230
+ size (int): The maximum number of alerts to return.
231
+ timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
232
+
233
+ Returns:
234
+ Dict[str, int]: A dictionary containing success status and the number of alerts per rule or an error message.
235
"""
236
indices_validation = self._collect_indices_and_validate()
237
if not indices_validation["success"]:
@@ -211,7 +239,7 @@ class AlertsService:
239
240
alerts_by_rule_dict = {}
241
for index_name in indices_validation["indices"]:
214
- alerts = self._collect_alerts(index_name=index_name, size=1000)
242
+ alerts = self._collect_alerts(index_name=index_name, size=size, timerange=timerange)
243
if alerts["success"]:
244
for alert in alerts["alerts"]:
245
rule = alert["_source"]["rule_description"]
@@ -225,9 +253,16 @@ class AlertsService:
253
"alerts_by_rule": alerts_by_rule_list,
254
}
255
228
- def collect_alerts_by_rule_per_host(self) -> Dict[str, int]:
256
+ def collect_alerts_by_rule_per_host(self, size: int, timerange: str) -> Dict[str, int]:
257
"""
258
Collects the number of alerts per rule per host.
259
+
260
+ Args:
261
+ size (int): The maximum number of alerts to return.
262
+ timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
263
+
264
+ Returns:
265
+ Dict[str, int]: A dictionary containing success status and the number of alerts per rule per host or an error message.
266
"""
267
indices_validation = self._collect_indices_and_validate()
268
if not indices_validation["success"]:
@@ -235,7 +270,7 @@ class AlertsService:
270
271
alerts_by_rule_per_host_dict = {}
272
for index_name in indices_validation["indices"]:
238
- alerts = self._collect_alerts(index_name=index_name, size=1000)
273
+ alerts = self._collect_alerts(index_name=index_name, size=size, timerange=timerange)
274
if alerts["success"]:
275
for alert in alerts["alerts"]:
276
rule = alert["_source"]["rule_description"]
@@ -261,7 +296,47 @@ class AlertsService:
296
"""
297
return {"message": message, "success": False}
298
264
- def _collect_alerts(self, index_name: str, size: int = None, timerange: str = "24h") -> Dict[str, object]:
299
+ # def _collect_alerts(self, index_name: str, size: int = None, timerange: str = "24h") -> Dict[str, object]:
300
+ # """
301
+ # Elasticsearch query to get the most recent alerts where the `rule_level` is 12 or higher or the
302
+ # `syslog_level` field is `ALERT` and return the results in descending order by the `timestamp_utc` field.
303
+ # The number of alerts to return can be limited by the `size` parameter.
304
+
305
+ # Args:
306
+ # index_name (str): The name of the index to query.
307
+ # size (int, optional): The maximum number of alerts to return. If None, all alerts are returned.
308
+ # timerange (str, optional): The time range to collect alerts from. This is a string like "24h", "1w", etc.
309
+
310
+ # Returns:
311
+ # Dict[str, object]: A dictionary containing success status and alerts or an error message.
312
+ # """
313
+ # logger.info(f"Collecting alerts from {index_name}")
314
+ # query = self._build_query(timerange=timerange) # Use the provided query
315
+ # try:
316
+ # alerts = self.es.search(index=index_name, body=query, size=size)
317
+ # alerts_list = [alert for alert in alerts["hits"]["hits"]]
318
+
319
+ # # Iterate over each alert and invoke invoke_socfortress function
320
+ # for alert in alerts_list:
321
+ # ask_socfortress = self.asksocfortress_service.invoke_asksocfortress(alert["_source"]["rule_description"])
322
+ # alert["ask_socfortress"] = ask_socfortress # Add the result to the alert
323
+
324
+ # return {
325
+ # "message": "Successfully collected alerts",
326
+ # "success": True,
327
+ # "alerts": alerts_list, # Return the alerts list with the added results
328
+ # }
329
+ # except Exception as e:
330
+ # logger.error(f"Failed to collect alerts: {e}")
331
+ # return {"message": "Failed to collect alerts", "success": False}
332
+
333
+ def _collect_alerts(
334
+ self,
335
+ index_name: str,
336
+ size: int = None,
337
+ timerange: str = "24h",
338
+ matches: Iterable[Tuple[str, str]] = None,
339
+ ) -> Dict[str, object]:
340
"""
341
Elasticsearch query to get the most recent alerts where the `rule_level` is 12 or higher or the
342
`syslog_level` field is `ALERT` and return the results in descending order by the `timestamp_utc` field.
@@ -271,12 +346,27 @@ class AlertsService:
346
index_name (str): The name of the index to query.
347
size (int, optional): The maximum number of alerts to return. If None, all alerts are returned.
348
timerange (str, optional): The time range to collect alerts from. This is a string like "24h", "1w", etc.
349
+ matches (Iterable[Tuple[str, str]], optional): A list of tuples representing the field and value to match.
350
+ I.E: [("syslog_level", "ALERT"), ("agent_name", "WIN-39O01J5F7G5")]
351
352
Returns:
353
Dict[str, object]: A dictionary containing success status and alerts or an error message.
354
"""
355
logger.info(f"Collecting alerts from {index_name}")
279
- query = self._build_query(timerange=timerange) # Use the provided query
356
+
357
+ # Use QueryBuilder to construct the query
358
+ query_builder = QueryBuilder()
359
+ query_builder.add_time_range(timerange)
360
+ if matches is not None:
361
+ query_builder.add_matches(matches)
362
+ else:
363
+ query_builder.add_matches([("syslog_level", "ALERT")])
364
+ query_builder.add_range("rule_level", "12")
365
+ query_builder.add_sort("timestamp_utc")
366
+
367
+ # Get the final query
368
+ query = query_builder.build()
369
+
370
try:
371
alerts = self.es.search(index=index_name, body=query, size=size)
372
alerts_list = [alert for alert in alerts["hits"]["hits"]]
@@ -314,28 +404,28 @@ class AlertsService:
404
logger.error(f"Failed to collect alert: {e}")
405
return {"message": "Failed to collect alert", "success": False}
406
317
- @staticmethod
318
- def _get_time_range_start(timerange: str) -> str:
319
- """
320
- Determines the start time of the time range based on the current time and the provided timerange.
321
-
322
- Args:
323
- timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
324
-
325
- Returns:
326
- str: A string representing the start time of the time range in ISO format.
327
- """
328
- if timerange.endswith("h"):
329
- delta = timedelta(hours=int(timerange[:-1]))
330
- elif timerange.endswith("d"):
331
- delta = timedelta(days=int(timerange[:-1]))
332
- elif timerange.endswith("w"):
333
- delta = timedelta(weeks=int(timerange[:-1]))
334
- else:
335
- raise ValueError("Invalid timerange format. Expected a string like '24h', '1d', '1w', etc.")
336
-
337
- start = datetime.utcnow() - delta
338
- return start.isoformat() + "Z" # Elasticsearch expects the time in ISO format with a Z at the end
407
+ # @staticmethod
408
+ # def _get_time_range_start(timerange: str) -> str:
409
+ # """
410
+ # Determines the start time of the time range based on the current time and the provided timerange.
411
+
412
+ # Args:
413
+ # timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
414
+
415
+ # Returns:
416
+ # str: A string representing the start time of the time range in ISO format.
417
+ # """
418
+ # if timerange.endswith("h"):
419
+ # delta = timedelta(hours=int(timerange[:-1]))
420
+ # elif timerange.endswith("d"):
421
+ # delta = timedelta(days=int(timerange[:-1]))
422
+ # elif timerange.endswith("w"):
423
+ # delta = timedelta(weeks=int(timerange[:-1]))
424
+ # else:
425
+ # raise ValueError("Invalid timerange format. Expected a string like '24h', '1d', '1w', etc.")
426
+
427
+ # start = datetime.utcnow() - delta
428
+ # return start.isoformat() + "Z" # Elasticsearch expects the time in ISO format with a Z at the end
429
430
@staticmethod
431
def _build_query(timerange: str) -> Dict[str, object]:
backend/app/services/WazuhIndexer/universal.py
+62
@@ -1,5 +1,9 @@
1
# from datetime import datetime
2
+from datetime import datetime
3
+from datetime import timedelta
4
from typing import Dict
5
+from typing import Iterable
6
+from typing import Tuple
7
8
# import requests
9
from elasticsearch7 import Elasticsearch
@@ -11,6 +15,8 @@ from loguru import logger
15
from app.models.connectors import Connector
16
from app.models.connectors import connector_factory
17
18
+# from app.services.WazuhIndexer.alerts import AlertsService
19
+
20
# from typing import List
21
22
@@ -150,3 +156,59 @@ class UniversalService:
156
except Exception as e:
157
logger.error(f"Failed to run query: {e}")
158
return {"message": "Failed to run query", "success": False}
159
+
160
+
161
+class QueryBuilder:
162
+ @staticmethod
163
+ def _get_time_range_start(timerange: str) -> str:
164
+ """
165
+ Determines the start time of the time range based on the current time and the provided timerange.
166
+
167
+ Args:
168
+ timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
169
+
170
+ Returns:
171
+ str: A string representing the start time of the time range in ISO format.
172
+ """
173
+ if timerange.endswith("h"):
174
+ delta = timedelta(hours=int(timerange[:-1]))
175
+ elif timerange.endswith("d"):
176
+ delta = timedelta(days=int(timerange[:-1]))
177
+ elif timerange.endswith("w"):
178
+ delta = timedelta(weeks=int(timerange[:-1]))
179
+ else:
180
+ raise ValueError("Invalid timerange format. Expected a string like '24h', '1d', '1w', etc.")
181
+
182
+ start = datetime.utcnow() - delta
183
+ return start.isoformat() + "Z" # Elasticsearch expects the time in ISO format with a Z at the end
184
+
185
+ def __init__(self):
186
+ self.query = {
187
+ "query": {
188
+ "bool": {
189
+ "must": [],
190
+ },
191
+ },
192
+ "sort": [],
193
+ }
194
+
195
+ def add_time_range(self, timerange: str):
196
+ start = self._get_time_range_start(timerange)
197
+ self.query["query"]["bool"]["must"].append({"range": {"timestamp_utc": {"gte": start, "lte": "now"}}})
198
+ return self
199
+
200
+ def add_matches(self, matches: Iterable[Tuple[str, str]]):
201
+ for field, value in matches:
202
+ self.query["query"]["bool"]["must"].append({"match": {field: value}})
203
+ return self
204
+
205
+ def add_range(self, field: str, value: str):
206
+ self.query["query"]["bool"]["must"].append({"range": {field: {"gte": value}}})
207
+ return self
208
+
209
+ def add_sort(self, field: str, order: str = "desc"):
210
+ self.query["sort"].append({field: {"order": order}})
211
+ return self
212
+
213
+ def build(self):
214
+ return self.query
backend/app/static/swagger.json
+130
-41
@@ -949,7 +949,29 @@
949
"/alerts": {
950
"get": {
951
"summary": "Get alerts",
952
- "description": "Endpoint to get alerts.",
952
+ "description": "Endpoint to get alerts. Retrieves all alerts from the AlertsService.",
953
+ "parameters": [
954
+ {
955
+ "name": "size",
956
+ "in": "query",
957
+ "description": "The number of alerts to return. Defaults to 10.",
958
+ "required": false,
959
+ "schema": {
960
+ "type": "integer",
961
+ "default": 10
962
+ }
963
+ },
964
+ {
965
+ "name": "timerange",
966
+ "in": "query",
967
+ "description": "The time range to consider for alerts. Defaults to '24h'.",
968
+ "required": false,
969
+ "schema": {
970
+ "type": "string",
971
+ "default": "24h"
972
+ }
973
+ }
974
+ ],
975
"responses": {
976
"200": {
977
"description": "Successful operation",
@@ -996,6 +1018,26 @@
1018
"description": "The name of the host to get alerts from.",
1019
"required": true,
1020
"type": "string"
1021
+ },
1022
+ {
1023
+ "name": "size",
1024
+ "in": "query",
1025
+ "description": "The number of alerts to return. Defaults to 10.",
1026
+ "required": false,
1027
+ "schema": {
1028
+ "type": "integer",
1029
+ "default": 10
1030
+ }
1031
+ },
1032
+ {
1033
+ "name": "timerange",
1034
+ "in": "query",
1035
+ "description": "The time range to consider for alerts. Defaults to '24h'.",
1036
+ "required": false,
1037
+ "schema": {
1038
+ "type": "string",
1039
+ "default": "24h"
1040
+ }
1041
}
1042
],
1043
"responses": {
@@ -1065,46 +1107,7 @@
1107
}
1108
}
1109
},
1068
- "operationId": "getAlertsFromIndex",
1069
- "tags": ["Wazuh-Indexer"]
1070
- }
1071
- },
1072
- "/alerts/top_10": {
1073
- "get": {
1074
- "summary": "Get top 10 alerts",
1075
- "description": "Endpoint to get top 10 alerts.",
1076
- "responses": {
1077
- "200": {
1078
- "description": "Successful operation",
1079
- "content": {
1080
- "application/json": {
1081
- "schema": {
1082
- "type": "object",
1083
- "properties": {
1084
- "alerts": {
1085
- "type": "array",
1086
- "items": {
1087
- "type": "object",
1088
- "description": "Alert details"
1089
- }
1090
- }
1091
- }
1092
- }
1093
- }
1094
- }
1095
- },
1096
- "default": {
1097
- "description": "Unexpected error",
1098
- "content": {
1099
- "application/json": {
1100
- "schema": {
1101
- "$ref": "#/components/schemas/Error"
1102
- }
1103
- }
1104
- }
1105
- }
1106
- },
1107
- "operationId": "getTop10Alerts",
1110
+ "operationId": "getAlertsFromAgent",
1111
"tags": ["Wazuh-Indexer"]
1112
}
1113
},
@@ -1119,6 +1122,26 @@
1122
"description": "The name of the index to get alerts from.",
1123
"required": true,
1124
"type": "string"
1125
+ },
1126
+ {
1127
+ "name": "size",
1128
+ "in": "query",
1129
+ "description": "The number of alerts to return. Defaults to 10.",
1130
+ "required": false,
1131
+ "schema": {
1132
+ "type": "integer",
1133
+ "default": 10
1134
+ }
1135
+ },
1136
+ {
1137
+ "name": "timerange",
1138
+ "in": "query",
1139
+ "description": "The time range to consider for alerts. Defaults to '24h'.",
1140
+ "required": false,
1141
+ "schema": {
1142
+ "type": "string",
1143
+ "default": "24h"
1144
+ }
1145
}
1146
],
1147
"responses": {
@@ -1196,6 +1219,28 @@
1219
"get": {
1220
"summary": "Get hosts with alerts",
1221
"description": "Endpoint to get hosts with alerts.",
1222
+ "parameters": [
1223
+ {
1224
+ "name": "size",
1225
+ "in": "query",
1226
+ "description": "The number of alerts to return. Defaults to 10.",
1227
+ "required": false,
1228
+ "schema": {
1229
+ "type": "integer",
1230
+ "default": 10
1231
+ }
1232
+ },
1233
+ {
1234
+ "name": "timerange",
1235
+ "in": "query",
1236
+ "description": "The time range to consider for alerts. Defaults to '24h'.",
1237
+ "required": false,
1238
+ "schema": {
1239
+ "type": "string",
1240
+ "default": "24h"
1241
+ }
1242
+ }
1243
+ ],
1244
"responses": {
1245
"200": {
1246
"description": "Successful operation",
@@ -1235,6 +1280,28 @@
1280
"get": {
1281
"summary": "Get rules with alerts",
1282
"description": "Endpoint to get rules with alerts.",
1283
+ "parameters": [
1284
+ {
1285
+ "name": "size",
1286
+ "in": "query",
1287
+ "description": "The number of alerts to return. Defaults to 10.",
1288
+ "required": false,
1289
+ "schema": {
1290
+ "type": "integer",
1291
+ "default": 10
1292
+ }
1293
+ },
1294
+ {
1295
+ "name": "timerange",
1296
+ "in": "query",
1297
+ "description": "The time range to consider for alerts. Defaults to '24h'.",
1298
+ "required": false,
1299
+ "schema": {
1300
+ "type": "string",
1301
+ "default": "24h"
1302
+ }
1303
+ }
1304
+ ],
1305
"responses": {
1306
"200": {
1307
"description": "Successful operation",
@@ -1274,6 +1341,28 @@
1341
"get": {
1342
"summary": "Get rules with alerts per host",
1343
"description": "Endpoint to get rules with alerts per host",
1344
+ "parameters": [
1345
+ {
1346
+ "name": "size",
1347
+ "in": "query",
1348
+ "description": "The number of alerts to return. Defaults to 10.",
1349
+ "required": false,
1350
+ "schema": {
1351
+ "type": "integer",
1352
+ "default": 10
1353
+ }
1354
+ },
1355
+ {
1356
+ "name": "timerange",
1357
+ "in": "query",
1358
+ "description": "The time range to consider for alerts. Defaults to '24h'.",
1359
+ "required": false,
1360
+ "schema": {
1361
+ "type": "string",
1362
+ "default": "24h"
1363
+ }
1364
+ }
1365
+ ],
1366
"responses": {
1367
"200": {
1368
"description": "Successful operation",