pingera-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pingera_cli/__init__.py +11 -0
- pingera_cli/__main__.py +9 -0
- pingera_cli/commands/__init__.py +11 -0
- pingera_cli/commands/auth.py +214 -0
- pingera_cli/commands/base.py +251 -0
- pingera_cli/commands/checks.py +1011 -0
- pingera_cli/commands/on_demand_checks.py +646 -0
- pingera_cli/formatters/__init__.py +13 -0
- pingera_cli/formatters/base_formatter.py +45 -0
- pingera_cli/formatters/generic_formatter.py +51 -0
- pingera_cli/formatters/multistep_formatter.py +141 -0
- pingera_cli/formatters/registry.py +35 -0
- pingera_cli/formatters/ssl_formatter.py +104 -0
- pingera_cli/formatters/synthetic_formatter.py +350 -0
- pingera_cli/formatters/web_formatter.py +78 -0
- pingera_cli/main.py +214 -0
- pingera_cli/utils/__init__.py +8 -0
- pingera_cli/utils/config.py +187 -0
- pingera_cli/utils/console.py +87 -0
- pingera_cli-0.1.0.dist-info/METADATA +199 -0
- pingera_cli-0.1.0.dist-info/RECORD +25 -0
- pingera_cli-0.1.0.dist-info/WHEEL +5 -0
- pingera_cli-0.1.0.dist-info/entry_points.txt +2 -0
- pingera_cli-0.1.0.dist-info/licenses/LICENSE +22 -0
- pingera_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Checks commands for PingeraCLI
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.prompt import Confirm
|
|
13
|
+
|
|
14
|
+
from .base import BaseCommand
|
|
15
|
+
from ..utils.config import get_api_key
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChecksCommand(BaseCommand):
|
|
19
|
+
"""
|
|
20
|
+
Commands for managing monitoring checks
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, output_format: Optional[str] = None):
|
|
24
|
+
super().__init__(output_format)
|
|
25
|
+
|
|
26
|
+
def get_client(self):
|
|
27
|
+
"""Get Pingera SDK client with authentication"""
|
|
28
|
+
api_key = get_api_key()
|
|
29
|
+
if not api_key:
|
|
30
|
+
self.display_error("API key not found. Use 'pngr auth login --api-key <key>' to set it.")
|
|
31
|
+
raise typer.Exit(1)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
from pingera import ApiClient, Configuration
|
|
35
|
+
from pingera.api import ChecksApi
|
|
36
|
+
|
|
37
|
+
# Configure the client
|
|
38
|
+
configuration = Configuration()
|
|
39
|
+
configuration.host = "https://api.pingera.ru"
|
|
40
|
+
configuration.api_key['apiKeyAuth'] = api_key
|
|
41
|
+
|
|
42
|
+
# Create API client
|
|
43
|
+
api_client = ApiClient(configuration)
|
|
44
|
+
return ChecksApi(api_client)
|
|
45
|
+
except ImportError:
|
|
46
|
+
self.display_error("Pingera SDK not installed. Install with: pip install pingera-sdk")
|
|
47
|
+
raise typer.Exit(1)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
self.display_error(f"Failed to initialize client: {str(e)}")
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
|
|
52
|
+
def get_unified_results_client(self):
|
|
53
|
+
"""Get Pingera SDK client for unified results with authentication"""
|
|
54
|
+
api_key = get_api_key()
|
|
55
|
+
if not api_key:
|
|
56
|
+
self.display_error("API key not found. Use 'pngr auth login --api-key <key>' to set it.")
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
from pingera import ApiClient, Configuration
|
|
61
|
+
from pingera.api import ChecksUnifiedResultsApi
|
|
62
|
+
|
|
63
|
+
# Configure the client
|
|
64
|
+
configuration = Configuration()
|
|
65
|
+
configuration.host = "https://api.pingera.ru"
|
|
66
|
+
configuration.api_key['apiKeyAuth'] = api_key
|
|
67
|
+
|
|
68
|
+
# Create API client
|
|
69
|
+
api_client = ApiClient(configuration)
|
|
70
|
+
return ChecksUnifiedResultsApi(api_client)
|
|
71
|
+
except ImportError:
|
|
72
|
+
self.display_error("Pingera SDK not installed. Install with: pip install pingera-sdk")
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
self.display_error(f"Failed to initialize client: {str(e)}")
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
|
|
78
|
+
def list_checks(self, page: int = 1, page_size: int = 20, check_type: Optional[str] = None, status: Optional[str] = None):
|
|
79
|
+
"""List monitoring checks"""
|
|
80
|
+
try:
|
|
81
|
+
checks_api = self.get_client()
|
|
82
|
+
|
|
83
|
+
# Make API call using the actual SDK method
|
|
84
|
+
response = checks_api.v1_checks_get()
|
|
85
|
+
|
|
86
|
+
if not response.checks:
|
|
87
|
+
if self.output_format == 'json':
|
|
88
|
+
self.output_data({"checks": [], "total": 0, "message": "No checks found"})
|
|
89
|
+
elif self.output_format == 'yaml':
|
|
90
|
+
self.output_data({"checks": [], "total": 0, "message": "No checks found"})
|
|
91
|
+
else:
|
|
92
|
+
self.display_info("No checks found.")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
# Prepare data for different output formats
|
|
96
|
+
if self.output_format in ['json', 'yaml']:
|
|
97
|
+
checks_data = []
|
|
98
|
+
for check in response.checks:
|
|
99
|
+
check_dict = {
|
|
100
|
+
"id": str(check.id) if check.id else None,
|
|
101
|
+
"name": check.name if check.name else None,
|
|
102
|
+
"type": check.type if check.type else None,
|
|
103
|
+
"url": check.url if check.url else None,
|
|
104
|
+
"status": check.status if check.status else None,
|
|
105
|
+
"interval": check.interval if check.interval else None,
|
|
106
|
+
"created_at": check.created_at.isoformat() if hasattr(check, 'created_at') and check.created_at else None
|
|
107
|
+
}
|
|
108
|
+
checks_data.append(check_dict)
|
|
109
|
+
|
|
110
|
+
self.output_data({
|
|
111
|
+
"checks": checks_data,
|
|
112
|
+
"total": len(checks_data),
|
|
113
|
+
"page": page,
|
|
114
|
+
"page_size": page_size
|
|
115
|
+
})
|
|
116
|
+
else:
|
|
117
|
+
# Create table for default output
|
|
118
|
+
table = Table(title=f"Monitoring Checks")
|
|
119
|
+
table.add_column("ID", style="cyan")
|
|
120
|
+
table.add_column("Name", style="green")
|
|
121
|
+
table.add_column("Type", style="blue")
|
|
122
|
+
table.add_column("URL", style="yellow")
|
|
123
|
+
table.add_column("Active", style="magenta")
|
|
124
|
+
table.add_column("Interval", style="white")
|
|
125
|
+
table.add_column("Status", style="white")
|
|
126
|
+
table.add_column("Created", style="dim")
|
|
127
|
+
|
|
128
|
+
for check in response.checks:
|
|
129
|
+
# Convert all values to strings to avoid Rich rendering issues
|
|
130
|
+
check_id = str(check.id) if hasattr(check, 'id') and check.id else "-"
|
|
131
|
+
check_name = str(check.name) if hasattr(check, 'name') and check.name else "-"
|
|
132
|
+
check_type = str(check.type) if hasattr(check, 'type') and check.type else "-"
|
|
133
|
+
|
|
134
|
+
# Handle URL/Host display
|
|
135
|
+
check_url = "-"
|
|
136
|
+
if hasattr(check, 'url') and check.url:
|
|
137
|
+
check_url = str(check.url)
|
|
138
|
+
elif hasattr(check, 'host') and hasattr(check, 'port') and check.host and check.port:
|
|
139
|
+
check_url = f"{check.host}:{check.port}"
|
|
140
|
+
|
|
141
|
+
# Handle active status
|
|
142
|
+
active_status = "✅" if hasattr(check, 'active') and check.active else "❌"
|
|
143
|
+
|
|
144
|
+
# Handle interval
|
|
145
|
+
interval_display = f"{check.interval}s" if hasattr(check, 'interval') and check.interval else "-"
|
|
146
|
+
|
|
147
|
+
# Handle status
|
|
148
|
+
status_display = str(check.status) if hasattr(check, 'status') and check.status else "-"
|
|
149
|
+
|
|
150
|
+
# Handle created_at
|
|
151
|
+
created_display = "-"
|
|
152
|
+
if hasattr(check, 'created_at') and check.created_at:
|
|
153
|
+
try:
|
|
154
|
+
created_display = check.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
155
|
+
except (AttributeError, TypeError):
|
|
156
|
+
created_display = str(check.created_at)
|
|
157
|
+
|
|
158
|
+
table.add_row(
|
|
159
|
+
check_id,
|
|
160
|
+
check_name,
|
|
161
|
+
check_type,
|
|
162
|
+
check_url,
|
|
163
|
+
active_status,
|
|
164
|
+
interval_display,
|
|
165
|
+
status_display,
|
|
166
|
+
created_display
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
self.console.print(table)
|
|
170
|
+
self.console.print(f"\n[dim]Found {len(response.checks)} checks[/dim]")
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
self.display_error(f"Failed to list checks: {str(e)}")
|
|
174
|
+
raise typer.Exit(1)
|
|
175
|
+
|
|
176
|
+
def get_check(self, check_id: str):
|
|
177
|
+
"""Get specific check details"""
|
|
178
|
+
try:
|
|
179
|
+
checks_api = self.get_client()
|
|
180
|
+
check = checks_api.v1_checks_check_id_get(check_id=check_id)
|
|
181
|
+
|
|
182
|
+
# Prepare data for different output formats
|
|
183
|
+
if self.output_format in ['json', 'yaml']:
|
|
184
|
+
check_data = {
|
|
185
|
+
"id": str(check.id) if check.id else None,
|
|
186
|
+
"name": check.name if check.name else None,
|
|
187
|
+
"type": check.type if check.type else None,
|
|
188
|
+
"status": check.status if check.status else None,
|
|
189
|
+
"active": check.active if hasattr(check, 'active') else None,
|
|
190
|
+
"url": check.url if check.url else None,
|
|
191
|
+
"host": check.host if hasattr(check, 'host') and check.host else None,
|
|
192
|
+
"port": check.port if hasattr(check, 'port') and check.port else None,
|
|
193
|
+
"interval": check.interval if check.interval else None,
|
|
194
|
+
"timeout": check.timeout if check.timeout else None,
|
|
195
|
+
"created_at": check.created_at.isoformat() if hasattr(check, 'created_at') and check.created_at else None,
|
|
196
|
+
"updated_at": check.updated_at.isoformat() if hasattr(check, 'updated_at') and check.updated_at else None,
|
|
197
|
+
"last_checked_at": check.last_checked_at.isoformat() if hasattr(check, 'last_checked_at') and check.last_checked_at else None,
|
|
198
|
+
"parameters": check.parameters if hasattr(check, 'parameters') and check.parameters else None
|
|
199
|
+
}
|
|
200
|
+
self.output_data(check_data)
|
|
201
|
+
else:
|
|
202
|
+
# Rich formatted output for table format
|
|
203
|
+
status_color = "green" if check.status == "ok" else "red" if check.status == "error" else "yellow"
|
|
204
|
+
active_status = "[green]✓ Active[/green]" if check.active else "[red]✗ Inactive[/red]"
|
|
205
|
+
|
|
206
|
+
basic_info = f"""[bold cyan]Basic Information:[/bold cyan]
|
|
207
|
+
• ID: [white]{check.id}[/white]
|
|
208
|
+
• Name: [white]{check.name}[/white]
|
|
209
|
+
• Type: [blue]{check.type}[/blue]
|
|
210
|
+
• Status: [{status_color}]{check.status}[/{status_color}]
|
|
211
|
+
• Active: {active_status}
|
|
212
|
+
• URL: [yellow]{check.url if check.url else 'N/A'}[/yellow]
|
|
213
|
+
• Host: [white]{check.host if hasattr(check, 'host') and check.host else 'N/A'}[/white]
|
|
214
|
+
• Port: [white]{check.port if hasattr(check, 'port') and check.port else 'N/A'}[/white]"""
|
|
215
|
+
|
|
216
|
+
# Timing information
|
|
217
|
+
timing_info = f"""[bold cyan]Timing & Monitoring:[/bold cyan]
|
|
218
|
+
• Interval: [white]{check.interval}s[/white] ({check.interval // 60} minutes)
|
|
219
|
+
• Timeout: [white]{check.timeout}s[/white]
|
|
220
|
+
• Created: [white]{check.created_at.strftime('%Y-%m-%d %H:%M:%S UTC') if check.created_at else 'Unknown'}[/white]
|
|
221
|
+
• Updated: [white]{check.updated_at.strftime('%Y-%m-%d %H:%M:%S UTC') if hasattr(check, 'updated_at') and check.updated_at else 'Unknown'}[/white]
|
|
222
|
+
• Last Checked: [white]{check.last_checked_at.strftime('%Y-%m-%d %H:%M:%S UTC') if hasattr(check, 'last_checked_at') and check.last_checked_at else 'Never'}[/white]"""
|
|
223
|
+
|
|
224
|
+
# Parameters section
|
|
225
|
+
parameters_info = "[bold cyan]Parameters:[/bold cyan]\n"
|
|
226
|
+
if hasattr(check, 'parameters') and check.parameters:
|
|
227
|
+
params = check.parameters
|
|
228
|
+
|
|
229
|
+
# Handle different check types
|
|
230
|
+
if check.type == 'web' or check.type == 'api':
|
|
231
|
+
if 'http_request' in params:
|
|
232
|
+
http_req = params['http_request']
|
|
233
|
+
parameters_info += f"• Method: [white]{http_req.get('method', 'GET')}[/white]\n"
|
|
234
|
+
if http_req.get('headers'):
|
|
235
|
+
parameters_info += "• Headers:\n"
|
|
236
|
+
for key, value in http_req['headers'].items():
|
|
237
|
+
# Mask sensitive headers
|
|
238
|
+
if key.lower() in ['authorization', 'x-api-key', 'token']:
|
|
239
|
+
value = "***" + value[-4:] if len(value) > 4 else "***"
|
|
240
|
+
parameters_info += f" - {key}: [dim]{value}[/dim]\n"
|
|
241
|
+
if http_req.get('body'):
|
|
242
|
+
# Show more of the body content, only truncate if very long
|
|
243
|
+
if len(http_req['body']) > 500:
|
|
244
|
+
body_preview = http_req['body'][:500] + "..."
|
|
245
|
+
else:
|
|
246
|
+
body_preview = http_req['body']
|
|
247
|
+
parameters_info += f"• Body: [dim]{body_preview}[/dim]\n"
|
|
248
|
+
if http_req.get('username'):
|
|
249
|
+
parameters_info += f"• Username: [white]{http_req['username']}[/white]\n"
|
|
250
|
+
|
|
251
|
+
if 'search_text' in params and params['search_text']:
|
|
252
|
+
parameters_info += f"• Search Text: [white]{params['search_text']}[/white]\n"
|
|
253
|
+
|
|
254
|
+
if 'regions' in params:
|
|
255
|
+
parameters_info += f"• Regions: [white]{', '.join(params['regions'])}[/white]\n"
|
|
256
|
+
|
|
257
|
+
elif check.type == 'multistep' or check.type == 'synthetic':
|
|
258
|
+
if 'pw_script' in params:
|
|
259
|
+
# Display full script without truncation
|
|
260
|
+
parameters_info += f"• Playwright Script:\n[dim]{params['pw_script']}[/dim]\n"
|
|
261
|
+
if 'regions' in params:
|
|
262
|
+
parameters_info += f"• Regions: [white]{', '.join(params['regions'])}[/white]\n"
|
|
263
|
+
|
|
264
|
+
elif check.type == 'ssl':
|
|
265
|
+
if 'assertions' in params:
|
|
266
|
+
assertions = params['assertions']
|
|
267
|
+
if 'expiration_threshold' in assertions:
|
|
268
|
+
days = assertions['expiration_threshold'] // 86400
|
|
269
|
+
parameters_info += f"• Expiration Threshold: [white]{days} days[/white]\n"
|
|
270
|
+
|
|
271
|
+
# Show any other parameters
|
|
272
|
+
for key, value in params.items():
|
|
273
|
+
if key not in ['http_request', 'search_text', 'regions', 'pw_script', 'assertions']:
|
|
274
|
+
parameters_info += f"• {key.replace('_', ' ').title()}: [white]{value}[/white]\n"
|
|
275
|
+
else:
|
|
276
|
+
parameters_info += "• [dim]No parameters configured[/dim]"
|
|
277
|
+
|
|
278
|
+
# Combine all sections
|
|
279
|
+
full_info = f"{basic_info}\n\n{timing_info}\n\n{parameters_info}"
|
|
280
|
+
|
|
281
|
+
panel = Panel(
|
|
282
|
+
full_info,
|
|
283
|
+
title=f"🔍 Check Details: {check.name}",
|
|
284
|
+
border_style="blue",
|
|
285
|
+
padding=(1, 2),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
self.console.print(panel)
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
self.display_error(f"Failed to get check: {str(e)}")
|
|
292
|
+
raise typer.Exit(1)
|
|
293
|
+
|
|
294
|
+
def create_check(self, name: str, check_type: str, url: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, interval: int = 300, timeout: int = 30, parameters: Optional[str] = None, pw_script_file: Optional[str] = None):
|
|
295
|
+
"""Create a new monitoring check"""
|
|
296
|
+
try:
|
|
297
|
+
import json
|
|
298
|
+
import os
|
|
299
|
+
checks_api = self.get_client()
|
|
300
|
+
|
|
301
|
+
# Build check data
|
|
302
|
+
check_data = {
|
|
303
|
+
"name": name,
|
|
304
|
+
"type": check_type,
|
|
305
|
+
"interval": interval,
|
|
306
|
+
"timeout": timeout
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# Add URL if provided (required for web, api, ssl checks)
|
|
310
|
+
if url is not None:
|
|
311
|
+
check_data["url"] = url
|
|
312
|
+
|
|
313
|
+
# Add host/port for TCP/SSL checks
|
|
314
|
+
if host is not None:
|
|
315
|
+
check_data["host"] = host
|
|
316
|
+
if port is not None:
|
|
317
|
+
check_data["port"] = port
|
|
318
|
+
|
|
319
|
+
# Handle pw_script_file option
|
|
320
|
+
params_dict = {}
|
|
321
|
+
if pw_script_file is not None:
|
|
322
|
+
if not os.path.exists(pw_script_file):
|
|
323
|
+
self.display_error(f"Playwright script file not found: {pw_script_file}")
|
|
324
|
+
raise typer.Exit(1)
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
with open(pw_script_file, 'r', encoding='utf-8') as f:
|
|
328
|
+
pw_script_content = f.read().strip()
|
|
329
|
+
|
|
330
|
+
if not pw_script_content:
|
|
331
|
+
self.display_error(f"Playwright script file is empty: {pw_script_file}")
|
|
332
|
+
raise typer.Exit(1)
|
|
333
|
+
|
|
334
|
+
params_dict["pw_script"] = pw_script_content
|
|
335
|
+
self.display_info(f"Loaded Playwright script from: {pw_script_file}")
|
|
336
|
+
|
|
337
|
+
except IOError as e:
|
|
338
|
+
self.display_error(f"Failed to read Playwright script file: {str(e)}")
|
|
339
|
+
raise typer.Exit(1)
|
|
340
|
+
|
|
341
|
+
# Parse parameters JSON if provided
|
|
342
|
+
if parameters is not None:
|
|
343
|
+
try:
|
|
344
|
+
parsed_params = json.loads(parameters)
|
|
345
|
+
# Merge with pw_script from file if both are provided
|
|
346
|
+
params_dict.update(parsed_params)
|
|
347
|
+
except json.JSONDecodeError as e:
|
|
348
|
+
self.display_error(f"Invalid JSON in --parameters: {str(e)}")
|
|
349
|
+
self.display_info("Example: --parameters '{\"pw_script\": \"const { test } = require('@playwright/test'); test('example', async ({ page }) => { await page.goto('https://example.com'); });\", \"regions\": [\"US\", \"EU\"]}'")
|
|
350
|
+
raise typer.Exit(1)
|
|
351
|
+
|
|
352
|
+
# Add parameters to check_data if we have any
|
|
353
|
+
if params_dict:
|
|
354
|
+
check_data["parameters"] = params_dict
|
|
355
|
+
|
|
356
|
+
# Validation based on check type
|
|
357
|
+
if check_type in ['web', 'api'] and not url:
|
|
358
|
+
self.display_error(f"URL is required for {check_type} checks")
|
|
359
|
+
raise typer.Exit(1)
|
|
360
|
+
|
|
361
|
+
if check_type in ['tcp'] and not host:
|
|
362
|
+
self.display_error(f"Host is required for {check_type} checks")
|
|
363
|
+
raise typer.Exit(1)
|
|
364
|
+
|
|
365
|
+
if check_type in ['ssl'] and not (url or host):
|
|
366
|
+
self.display_error(f"Either URL or host is required for {check_type} checks")
|
|
367
|
+
raise typer.Exit(1)
|
|
368
|
+
|
|
369
|
+
if check_type in ['synthetic', 'multistep'] and not params_dict.get('pw_script'):
|
|
370
|
+
self.display_error(f"Playwright script is required for {check_type} checks. Use --pw-script-file or --parameters with pw_script")
|
|
371
|
+
raise typer.Exit(1)
|
|
372
|
+
|
|
373
|
+
# Use the actual SDK method
|
|
374
|
+
check = checks_api.v1_checks_post(monitor_check=check_data)
|
|
375
|
+
|
|
376
|
+
# Build success message
|
|
377
|
+
success_details = [f"ID: {check.id}", f"Type: {check_type}"]
|
|
378
|
+
if url:
|
|
379
|
+
success_details.append(f"URL: {url}")
|
|
380
|
+
if host:
|
|
381
|
+
success_details.append(f"Host: {host}")
|
|
382
|
+
if port:
|
|
383
|
+
success_details.append(f"Port: {port}")
|
|
384
|
+
if pw_script_file:
|
|
385
|
+
success_details.append(f"Script: loaded from {pw_script_file}")
|
|
386
|
+
|
|
387
|
+
self.display_success(
|
|
388
|
+
f"Check '{name}' created successfully!\n" + "\n".join(success_details),
|
|
389
|
+
"✅ Check Created"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
except Exception as e:
|
|
393
|
+
self.display_error(f"Failed to create check: {str(e)}")
|
|
394
|
+
raise typer.Exit(1)
|
|
395
|
+
|
|
396
|
+
def update_check(self, check_id: str, name: Optional[str] = None, url: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, interval: Optional[int] = None, timeout: Optional[int] = None, active: Optional[bool] = None, parameters: Optional[str] = None, pw_script_file: Optional[str] = None):
|
|
397
|
+
"""Update an existing check"""
|
|
398
|
+
try:
|
|
399
|
+
import json
|
|
400
|
+
import os
|
|
401
|
+
checks_api = self.get_client()
|
|
402
|
+
|
|
403
|
+
# Build update data
|
|
404
|
+
update_data = {}
|
|
405
|
+
if name is not None:
|
|
406
|
+
update_data["name"] = name
|
|
407
|
+
if url is not None:
|
|
408
|
+
update_data["url"] = url
|
|
409
|
+
if host is not None:
|
|
410
|
+
update_data["host"] = host
|
|
411
|
+
if port is not None:
|
|
412
|
+
update_data["port"] = port
|
|
413
|
+
if interval is not None:
|
|
414
|
+
update_data["interval"] = interval
|
|
415
|
+
if timeout is not None:
|
|
416
|
+
update_data["timeout"] = timeout
|
|
417
|
+
if active is not None:
|
|
418
|
+
update_data["active"] = active
|
|
419
|
+
|
|
420
|
+
# Handle pw_script_file option
|
|
421
|
+
params_dict = {}
|
|
422
|
+
if pw_script_file is not None:
|
|
423
|
+
if not os.path.exists(pw_script_file):
|
|
424
|
+
self.display_error(f"Playwright script file not found: {pw_script_file}")
|
|
425
|
+
raise typer.Exit(1)
|
|
426
|
+
|
|
427
|
+
try:
|
|
428
|
+
with open(pw_script_file, 'r', encoding='utf-8') as f:
|
|
429
|
+
pw_script_content = f.read().strip()
|
|
430
|
+
|
|
431
|
+
if not pw_script_content:
|
|
432
|
+
self.display_error(f"Playwright script file is empty: {pw_script_file}")
|
|
433
|
+
raise typer.Exit(1)
|
|
434
|
+
|
|
435
|
+
params_dict["pw_script"] = pw_script_content
|
|
436
|
+
self.display_info(f"Loaded Playwright script from: {pw_script_file}")
|
|
437
|
+
|
|
438
|
+
except IOError as e:
|
|
439
|
+
self.display_error(f"Failed to read Playwright script file: {str(e)}")
|
|
440
|
+
raise typer.Exit(1)
|
|
441
|
+
|
|
442
|
+
# Parse parameters JSON if provided
|
|
443
|
+
if parameters is not None:
|
|
444
|
+
try:
|
|
445
|
+
parsed_params = json.loads(parameters)
|
|
446
|
+
# Merge with pw_script from file if both are provided
|
|
447
|
+
params_dict.update(parsed_params)
|
|
448
|
+
except json.JSONDecodeError as e:
|
|
449
|
+
self.display_error(f"Invalid JSON in --parameters: {str(e)}")
|
|
450
|
+
self.display_info("Example: --parameters '{\"pw_script\": \"const { test } = require('@playwright/test'); test('example', async ({ page }) => { await page.goto('https://example.com'); });\", \"regions\": [\"US\", \"EU\"]}'")
|
|
451
|
+
raise typer.Exit(1)
|
|
452
|
+
|
|
453
|
+
# Add parameters to update_data if we have any
|
|
454
|
+
if params_dict:
|
|
455
|
+
update_data["parameters"] = params_dict
|
|
456
|
+
|
|
457
|
+
if not update_data:
|
|
458
|
+
self.display_warning("No updates specified. Use --name, --url, --host, --port, --interval, --timeout, --active/--inactive, --parameters, or --pw-script-file to update.")
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
# Use the actual SDK method with the correct parameter name
|
|
462
|
+
check = checks_api.v1_checks_check_id_patch(check_id=check_id, monitor_check1=update_data)
|
|
463
|
+
|
|
464
|
+
status_msg = ""
|
|
465
|
+
if active is not None:
|
|
466
|
+
status_msg = f"\nStatus: {'Active' if active else 'Paused'}"
|
|
467
|
+
|
|
468
|
+
# Show what was updated
|
|
469
|
+
updated_fields = []
|
|
470
|
+
if name is not None:
|
|
471
|
+
updated_fields.append(f"name: {check.name}")
|
|
472
|
+
if url is not None:
|
|
473
|
+
updated_fields.append(f"url: {check.url}")
|
|
474
|
+
if host is not None:
|
|
475
|
+
updated_fields.append(f"host: {check.host}")
|
|
476
|
+
if port is not None:
|
|
477
|
+
updated_fields.append(f"port: {check.port}")
|
|
478
|
+
if interval is not None:
|
|
479
|
+
updated_fields.append(f"interval: {check.interval}s")
|
|
480
|
+
if timeout is not None:
|
|
481
|
+
updated_fields.append(f"timeout: {check.timeout}s")
|
|
482
|
+
if pw_script_file is not None:
|
|
483
|
+
updated_fields.append(f"pw_script: loaded from {pw_script_file}")
|
|
484
|
+
if parameters is not None:
|
|
485
|
+
updated_fields.append("parameters: updated")
|
|
486
|
+
|
|
487
|
+
update_summary = "\n".join([f"• {field}" for field in updated_fields]) if updated_fields else ""
|
|
488
|
+
|
|
489
|
+
self.display_success(
|
|
490
|
+
f"Check {check_id} updated successfully!\n{update_summary}{status_msg}",
|
|
491
|
+
"✅ Check Updated"
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
except Exception as e:
|
|
495
|
+
self.display_error(f"Failed to update check: {str(e)}")
|
|
496
|
+
raise typer.Exit(1)
|
|
497
|
+
|
|
498
|
+
def delete_check(self, check_id: str, confirm: bool = False):
|
|
499
|
+
"""Delete a monitoring check"""
|
|
500
|
+
try:
|
|
501
|
+
if not confirm:
|
|
502
|
+
if not Confirm.ask(f"Are you sure you want to delete check {check_id}?"):
|
|
503
|
+
self.console.print("[yellow]Operation cancelled.[/yellow]")
|
|
504
|
+
return
|
|
505
|
+
|
|
506
|
+
client = self.get_client()
|
|
507
|
+
client.v1_checks_check_id_delete(check_id=check_id)
|
|
508
|
+
|
|
509
|
+
self.display_success(
|
|
510
|
+
f"Check {check_id} deleted successfully!",
|
|
511
|
+
"✅ Check Deleted"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
except Exception as e:
|
|
515
|
+
self.display_error(f"Failed to delete check: {str(e)}")
|
|
516
|
+
raise typer.Exit(1)
|
|
517
|
+
|
|
518
|
+
def get_check_results(self, check_id: Optional[str] = None, from_date: Optional[str] = None, to_date: Optional[str] = None, page: int = 1, page_size: int = 20, status: Optional[str] = None, check_type: Optional[str] = None, region: Optional[str] = None):
|
|
519
|
+
"""Get check results using unified results API"""
|
|
520
|
+
try:
|
|
521
|
+
unified_api = self.get_unified_results_client()
|
|
522
|
+
|
|
523
|
+
# Build parameters for the unified API call
|
|
524
|
+
params = {
|
|
525
|
+
"page": page,
|
|
526
|
+
"page_size": page_size
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
# Add check_id only if provided
|
|
530
|
+
if check_id:
|
|
531
|
+
params["check_id"] = check_id
|
|
532
|
+
|
|
533
|
+
# Add optional filters
|
|
534
|
+
if from_date:
|
|
535
|
+
params["start_date"] = from_date
|
|
536
|
+
if to_date:
|
|
537
|
+
params["end_date"] = to_date
|
|
538
|
+
if status:
|
|
539
|
+
params["status"] = status
|
|
540
|
+
if check_type:
|
|
541
|
+
params["check_type"] = check_type
|
|
542
|
+
if region:
|
|
543
|
+
params["region"] = region
|
|
544
|
+
|
|
545
|
+
# Use the unified results API
|
|
546
|
+
response = unified_api.v1_checks_all_results_get(**params)
|
|
547
|
+
|
|
548
|
+
if not hasattr(response, 'results') or not response.results:
|
|
549
|
+
if self.output_format in ['json', 'yaml']:
|
|
550
|
+
self.output_data({"results": [], "total": 0, "message": "No results found"})
|
|
551
|
+
else:
|
|
552
|
+
self.display_info("No results found.")
|
|
553
|
+
return
|
|
554
|
+
|
|
555
|
+
# Prepare data for different output formats
|
|
556
|
+
if self.output_format in ['json', 'yaml']:
|
|
557
|
+
results_data = []
|
|
558
|
+
for result in response.results:
|
|
559
|
+
result_dict = {
|
|
560
|
+
"id": str(result.id) if hasattr(result, 'id') and result.id else None,
|
|
561
|
+
"check_id": result.check_id if hasattr(result, 'check_id') else None,
|
|
562
|
+
"check_name": result.check_name if hasattr(result, 'check_name') else None,
|
|
563
|
+
"check_type": result.check_type if hasattr(result, 'check_type') else None,
|
|
564
|
+
"status": result.status if hasattr(result, 'status') else None,
|
|
565
|
+
"created_at": result.created_at if hasattr(result, 'created_at') and result.created_at else None,
|
|
566
|
+
"response_time": result.response_time if hasattr(result, 'response_time') else None,
|
|
567
|
+
"error_message": result.error_message if hasattr(result, 'error_message') else None,
|
|
568
|
+
"check_server_id": result.check_server_id if hasattr(result, 'check_server_id') else None,
|
|
569
|
+
"region": result.region if hasattr(result, 'region') else None,
|
|
570
|
+
"result_type": result.result_type if hasattr(result, 'result_type') else None,
|
|
571
|
+
"check_metadata": result.check_metadata if hasattr(result, 'check_metadata') else None,
|
|
572
|
+
"check_server": {
|
|
573
|
+
"ip_address": result.check_server.ip_address if hasattr(result, 'check_server') and result.check_server and hasattr(result.check_server, 'ip_address') else None,
|
|
574
|
+
"country": result.check_server.country if hasattr(result, 'check_server') and result.check_server and hasattr(result.check_server, 'country') else None,
|
|
575
|
+
"region": result.check_server.region if hasattr(result, 'check_server') and result.check_server and hasattr(result.check_server, 'region') else None
|
|
576
|
+
} if hasattr(result, 'check_server') and result.check_server else None
|
|
577
|
+
}
|
|
578
|
+
results_data.append(result_dict)
|
|
579
|
+
|
|
580
|
+
pagination_info = {}
|
|
581
|
+
if hasattr(response, 'pagination') and response.pagination:
|
|
582
|
+
pagination_info = {
|
|
583
|
+
"page": response.pagination.get('page', page),
|
|
584
|
+
"page_size": response.pagination.get('page_size', page_size),
|
|
585
|
+
"total_items": response.pagination.get('total_items', len(results_data)),
|
|
586
|
+
"total_pages": response.pagination.get('total_pages', 1)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
self.output_data({
|
|
590
|
+
"results": results_data,
|
|
591
|
+
"pagination": pagination_info if pagination_info else {"page": page, "page_size": page_size, "total_items": len(results_data)}
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
else:
|
|
595
|
+
# Create table for human-readable output
|
|
596
|
+
table_title = f"Check Results for {check_id}" if check_id else "All Check Results"
|
|
597
|
+
table = Table(title=table_title)
|
|
598
|
+
table.add_column("Result ID", style="dim", min_width=15)
|
|
599
|
+
table.add_column("Check/Job ID", style="cyan", min_width=15)
|
|
600
|
+
table.add_column("Timestamp", style="cyan")
|
|
601
|
+
table.add_column("Check Name", style="green", max_width=20)
|
|
602
|
+
table.add_column("Type", style="blue")
|
|
603
|
+
table.add_column("Status", style="green")
|
|
604
|
+
table.add_column("Response Time", style="yellow")
|
|
605
|
+
table.add_column("Region", style="magenta", max_width=12)
|
|
606
|
+
table.add_column("Error", style="red", max_width=25)
|
|
607
|
+
|
|
608
|
+
for result in response.results:
|
|
609
|
+
# Extract result ID - never truncate IDs
|
|
610
|
+
result_id = str(result.id) if hasattr(result, 'id') and result.id else "-"
|
|
611
|
+
|
|
612
|
+
# Extract check_id or job_id based on result type - never truncate IDs
|
|
613
|
+
check_or_job_id = "-"
|
|
614
|
+
if hasattr(result, 'check_id') and result.check_id:
|
|
615
|
+
check_or_job_id = str(result.check_id)
|
|
616
|
+
elif hasattr(result, 'result_type') and result.result_type == 'on_demand':
|
|
617
|
+
# For on-demand results, we might not have check_id, show as on-demand
|
|
618
|
+
check_or_job_id = "[dim]on-demand[/dim]"
|
|
619
|
+
|
|
620
|
+
# Extract timestamp
|
|
621
|
+
timestamp = result.created_at.strftime("%m-%d %H:%M:%S") if hasattr(result, 'created_at') and result.created_at else "-"
|
|
622
|
+
|
|
623
|
+
# Check name
|
|
624
|
+
check_name = result.check_name if hasattr(result, 'check_name') and result.check_name else "-"
|
|
625
|
+
if len(check_name) > 19:
|
|
626
|
+
check_name = check_name[:19] + "…"
|
|
627
|
+
|
|
628
|
+
# Check type
|
|
629
|
+
check_type_display = result.check_type if hasattr(result, 'check_type') and result.check_type else "-"
|
|
630
|
+
|
|
631
|
+
# Determine status with emoji
|
|
632
|
+
status_emoji = "✅" if hasattr(result, 'status') and result.status == 'ok' else "❌"
|
|
633
|
+
status_color = "green" if hasattr(result, 'status') and result.status == 'ok' else "red"
|
|
634
|
+
status_display = f"[{status_color}]{status_emoji} {result.status}[/{status_color}]" if hasattr(result, 'status') and result.status else "-"
|
|
635
|
+
|
|
636
|
+
# Extract response time
|
|
637
|
+
response_time = f"{result.response_time}ms" if hasattr(result, 'response_time') and result.response_time is not None else "-"
|
|
638
|
+
|
|
639
|
+
# Extract region
|
|
640
|
+
region_display = result.region if hasattr(result, 'region') and result.region else "-"
|
|
641
|
+
if len(region_display) > 11:
|
|
642
|
+
region_display = region_display[:11] + "…"
|
|
643
|
+
|
|
644
|
+
# Extract error message
|
|
645
|
+
error_message = "-"
|
|
646
|
+
if hasattr(result, 'error_message') and result.error_message:
|
|
647
|
+
error_message = result.error_message
|
|
648
|
+
# Truncate long error messages
|
|
649
|
+
if len(error_message) > 24:
|
|
650
|
+
error_message = error_message[:24] + "…"
|
|
651
|
+
|
|
652
|
+
table.add_row(
|
|
653
|
+
result_id,
|
|
654
|
+
check_or_job_id,
|
|
655
|
+
timestamp,
|
|
656
|
+
check_name,
|
|
657
|
+
check_type_display,
|
|
658
|
+
status_display,
|
|
659
|
+
response_time,
|
|
660
|
+
region_display,
|
|
661
|
+
error_message
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
self.console.print(table)
|
|
665
|
+
|
|
666
|
+
# Show pagination info and hint about detailed view
|
|
667
|
+
if hasattr(response, 'pagination') and response.pagination:
|
|
668
|
+
pagination = response.pagination
|
|
669
|
+
total_items = pagination.get('total_items', len(response.results))
|
|
670
|
+
total_pages = pagination.get('total_pages', 1)
|
|
671
|
+
current_page = pagination.get('page', page)
|
|
672
|
+
page_size_actual = pagination.get('page_size', page_size)
|
|
673
|
+
|
|
674
|
+
self.console.print(f"\n[dim]Showing {len(response.results)} results • Page {current_page} of {total_pages} • {total_items} total items • {page_size_actual} per page[/dim]")
|
|
675
|
+
else:
|
|
676
|
+
self.console.print(f"\n[dim]Found {len(response.results)} results[/dim]")
|
|
677
|
+
|
|
678
|
+
# Provide helpful hints based on context
|
|
679
|
+
hints = []
|
|
680
|
+
if check_id:
|
|
681
|
+
hints.append(f"Use 'pngr checks result {check_id} <result-id>' for detailed result info")
|
|
682
|
+
else:
|
|
683
|
+
hints.append("Use 'pngr checks result <check-id> <result-id>' for detailed result info")
|
|
684
|
+
hints.append("For on-demand checks: use 'pngr checks jobs result <result-id>' for detailed info")
|
|
685
|
+
hints.append("Use 'pngr checks list' to see all available checks and their IDs")
|
|
686
|
+
hints.append("Filter results: --type <type>, --status <status>, --region <region>")
|
|
687
|
+
|
|
688
|
+
for hint in hints:
|
|
689
|
+
self.console.print(f"[dim]💡 {hint}[/dim]")
|
|
690
|
+
|
|
691
|
+
except Exception as e:
|
|
692
|
+
self.display_error(f"Failed to get check results: {str(e)}")
|
|
693
|
+
raise typer.Exit(1)
|
|
694
|
+
|
|
695
|
+
def get_check_result(self, result_id: str):
|
|
696
|
+
"""Get detailed information for a specific check result"""
|
|
697
|
+
try:
|
|
698
|
+
checks_api = self.get_client()
|
|
699
|
+
|
|
700
|
+
# Parse the result ID to extract check_id and check_result_id
|
|
701
|
+
# The result_id format might be "check_id:result_id" or just "result_id"
|
|
702
|
+
# We need to determine the check_id somehow - for now, we'll ask user to provide it
|
|
703
|
+
|
|
704
|
+
# Since we need both check_id and check_result_id, we'll need to modify the command
|
|
705
|
+
# For now, let's show an error and guide the user
|
|
706
|
+
self.display_error("This command needs both check ID and result ID.")
|
|
707
|
+
self.display_info("Usage: pngr checks result <check-id> <result-id>")
|
|
708
|
+
self.display_info("Or use: pngr checks results <check-id> to list all results first")
|
|
709
|
+
return
|
|
710
|
+
|
|
711
|
+
except Exception as e:
|
|
712
|
+
self.display_error(f"Failed to get result details: {str(e)}")
|
|
713
|
+
raise typer.Exit(1)
|
|
714
|
+
|
|
715
|
+
def get_check_result_detailed(self, check_id: str, result_id: str, verbose: bool = False):
|
|
716
|
+
"""Get detailed information for a specific check result using check_id and result_id"""
|
|
717
|
+
try:
|
|
718
|
+
checks_api = self.get_client()
|
|
719
|
+
|
|
720
|
+
# Use the actual SDK method
|
|
721
|
+
result = checks_api.v1_checks_check_id_results_check_result_id_get(
|
|
722
|
+
check_id=check_id,
|
|
723
|
+
check_result_id=result_id
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
if self.output_format in ['json', 'yaml']:
|
|
727
|
+
# Full result data for JSON/YAML
|
|
728
|
+
result_data = {
|
|
729
|
+
"id": str(result.id) if hasattr(result, 'id') else None,
|
|
730
|
+
"check_id": result.check_id if hasattr(result, 'check_id') else None,
|
|
731
|
+
"status": result.status if hasattr(result, 'status') else None,
|
|
732
|
+
"created_at": result.created_at if hasattr(result, 'created_at') and result.created_at else None,
|
|
733
|
+
"response_time": result.response_time if hasattr(result, 'response_time') else None,
|
|
734
|
+
"error_message": result.error_message if hasattr(result, 'error_message') else None,
|
|
735
|
+
"check_server_id": result.check_server_id if hasattr(result, 'check_server_id') else None,
|
|
736
|
+
"check_metadata": result.check_metadata if hasattr(result, 'check_metadata') else None
|
|
737
|
+
}
|
|
738
|
+
self.output_data(result_data)
|
|
739
|
+
else:
|
|
740
|
+
# Rich formatted detailed view
|
|
741
|
+
self._display_detailed_result(result, verbose)
|
|
742
|
+
|
|
743
|
+
except Exception as e:
|
|
744
|
+
self.display_error(f"Failed to get result details: {str(e)}")
|
|
745
|
+
raise typer.Exit(1)
|
|
746
|
+
|
|
747
|
+
def _display_detailed_result(self, result, verbose: bool = False):
|
|
748
|
+
"""Display detailed result information in a rich format"""
|
|
749
|
+
|
|
750
|
+
# Basic information
|
|
751
|
+
response_time_display = f"{result.response_time}ms" if hasattr(result, 'response_time') and result.response_time else 'N/A'
|
|
752
|
+
basic_info = f"""[bold cyan]Basic Information:[/bold cyan]
|
|
753
|
+
• Result ID: [white]{result.id}[/white]
|
|
754
|
+
• Check ID: [white]{result.check_id}[/white]
|
|
755
|
+
• Status: [{"green" if result.status == "ok" else "red"}]{result.status}[/{"green" if result.status == "ok" else "red"}]
|
|
756
|
+
• Timestamp: [white]{result.created_at.strftime('%Y-%m-%d %H:%M:%S UTC') if result.created_at else 'Unknown'}[/white]
|
|
757
|
+
• Response Time: [yellow]{response_time_display}[/yellow]
|
|
758
|
+
• Check Server: [magenta]{result.check_server_id if result.check_server_id else 'Unknown'}[/magenta]"""
|
|
759
|
+
|
|
760
|
+
# Add check server details if available
|
|
761
|
+
if hasattr(result, 'check_server') and result.check_server:
|
|
762
|
+
server = result.check_server
|
|
763
|
+
basic_info += f"""
|
|
764
|
+
• Server IP: [white]{server.ip_address if hasattr(server, 'ip_address') else 'Unknown'}[/white]
|
|
765
|
+
• Server Country: [white]{server.country if hasattr(server, 'country') else 'Unknown'}[/white]
|
|
766
|
+
• Server Region: [white]{server.region if hasattr(server, 'region') else 'Unknown'}[/white]"""
|
|
767
|
+
|
|
768
|
+
# Error information
|
|
769
|
+
error_info = ""
|
|
770
|
+
if hasattr(result, 'error_message') and result.error_message:
|
|
771
|
+
error_info = f"""
|
|
772
|
+
[bold red]Error Information:[/bold red]
|
|
773
|
+
• Error: [red]{result.error_message}[/red]"""
|
|
774
|
+
|
|
775
|
+
# Metadata analysis - handle different check types
|
|
776
|
+
metadata_info = ""
|
|
777
|
+
if hasattr(result, 'check_metadata') and result.check_metadata:
|
|
778
|
+
metadata = result.check_metadata
|
|
779
|
+
|
|
780
|
+
if isinstance(metadata, dict):
|
|
781
|
+
metadata_info = self._format_metadata_by_type(metadata, verbose)
|
|
782
|
+
|
|
783
|
+
# Combine all sections
|
|
784
|
+
full_info = basic_info
|
|
785
|
+
if error_info:
|
|
786
|
+
full_info += error_info
|
|
787
|
+
if metadata_info:
|
|
788
|
+
full_info += metadata_info
|
|
789
|
+
|
|
790
|
+
panel = Panel(
|
|
791
|
+
full_info,
|
|
792
|
+
title=f"🔍 Result Details: {result.id}",
|
|
793
|
+
border_style="blue",
|
|
794
|
+
padding=(1, 2),
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
self.console.print(panel)
|
|
798
|
+
|
|
799
|
+
def _format_metadata_by_type(self, metadata, verbose: bool = False):
|
|
800
|
+
"""Format metadata using the appropriate formatter"""
|
|
801
|
+
from ..formatters.registry import FormatterRegistry
|
|
802
|
+
registry = FormatterRegistry(verbose)
|
|
803
|
+
return registry.format_metadata(metadata)
|
|
804
|
+
|
|
805
|
+
def list_regions(self, check_type: Optional[str] = None):
|
|
806
|
+
"""List available regions for checks"""
|
|
807
|
+
try:
|
|
808
|
+
checks_api = self.get_client()
|
|
809
|
+
|
|
810
|
+
# Get regions with optional check_type filter
|
|
811
|
+
if check_type:
|
|
812
|
+
response = checks_api.v1_checks_get_regions_get(check_type=check_type)
|
|
813
|
+
else:
|
|
814
|
+
response = checks_api.v1_checks_get_regions_get()
|
|
815
|
+
|
|
816
|
+
if not hasattr(response, 'regions') or not response.regions:
|
|
817
|
+
if self.output_format in ['json', 'yaml']:
|
|
818
|
+
self.output_data({"regions": [], "total": 0, "message": "No regions found"})
|
|
819
|
+
else:
|
|
820
|
+
self.display_info("No regions found.")
|
|
821
|
+
return
|
|
822
|
+
|
|
823
|
+
# Prepare data for different output formats
|
|
824
|
+
if self.output_format in ['json', 'yaml']:
|
|
825
|
+
regions_data = []
|
|
826
|
+
for region in response.regions:
|
|
827
|
+
region_dict = {
|
|
828
|
+
"id": str(region.id) if hasattr(region, 'id') and region.id else None,
|
|
829
|
+
"display_name": region.display_name if hasattr(region, 'display_name') and region.display_name else None,
|
|
830
|
+
"available_check_types": region.available_check_types if hasattr(region, 'available_check_types') and region.available_check_types else None
|
|
831
|
+
}
|
|
832
|
+
regions_data.append(region_dict)
|
|
833
|
+
|
|
834
|
+
self.output_data({
|
|
835
|
+
"regions": regions_data,
|
|
836
|
+
"total": len(regions_data),
|
|
837
|
+
"filter": {"check_type": check_type} if check_type else None
|
|
838
|
+
})
|
|
839
|
+
else:
|
|
840
|
+
# Create table for default output
|
|
841
|
+
table_title = f"Available Regions for {check_type.upper()} checks" if check_type else "Available Regions"
|
|
842
|
+
table = Table(title=table_title)
|
|
843
|
+
table.add_column("Region ID", style="cyan", min_width=15)
|
|
844
|
+
table.add_column("Display Name", style="green", min_width=25)
|
|
845
|
+
table.add_column("Available Check Types", style="white")
|
|
846
|
+
|
|
847
|
+
for region in response.regions:
|
|
848
|
+
# Extract region data using actual SDK attributes
|
|
849
|
+
region_id = str(region.id) if hasattr(region, 'id') and region.id else "-"
|
|
850
|
+
display_name = region.display_name if hasattr(region, 'display_name') and region.display_name else "-"
|
|
851
|
+
|
|
852
|
+
# Handle available check types
|
|
853
|
+
available_types = "-"
|
|
854
|
+
if hasattr(region, 'available_check_types') and region.available_check_types:
|
|
855
|
+
if isinstance(region.available_check_types, list):
|
|
856
|
+
available_types = ", ".join(region.available_check_types)
|
|
857
|
+
else:
|
|
858
|
+
available_types = str(region.available_check_types)
|
|
859
|
+
|
|
860
|
+
table.add_row(
|
|
861
|
+
region_id,
|
|
862
|
+
display_name,
|
|
863
|
+
available_types
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
self.console.print(table)
|
|
867
|
+
|
|
868
|
+
# Show summary
|
|
869
|
+
filter_text = f" for {check_type} checks" if check_type else ""
|
|
870
|
+
self.console.print(f"\n[dim]Found {len(response.regions)} regions{filter_text}[/dim]")
|
|
871
|
+
|
|
872
|
+
# Show helpful hints
|
|
873
|
+
if not check_type:
|
|
874
|
+
self.console.print(f"[dim]💡 Filter by check type: pngr checks list-regions --type <type>[/dim]")
|
|
875
|
+
self.console.print(f"[dim]💡 Available check types: web, api, tcp, ssl, synthetic, multistep[/dim]")
|
|
876
|
+
|
|
877
|
+
except Exception as e:
|
|
878
|
+
self.display_error(f"Failed to list regions: {str(e)}")
|
|
879
|
+
raise typer.Exit(1)
|
|
880
|
+
|
|
881
|
+
# Create Typer app for checks commands
|
|
882
|
+
app = typer.Typer(name="checks", help="🔍 Manage monitoring checks")
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def get_output_format():
|
|
886
|
+
"""Get output format from config"""
|
|
887
|
+
from ..utils.config import get_config
|
|
888
|
+
return get_config().get('output_format', 'table')
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
@app.command("list")
|
|
892
|
+
def list_checks(
|
|
893
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
894
|
+
page_size: int = typer.Option(20, "--page-size", "-s", help="Items per page (max 100)"),
|
|
895
|
+
check_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by check type"),
|
|
896
|
+
status: Optional[str] = typer.Option(None, "--status", help="Filter by status"),
|
|
897
|
+
):
|
|
898
|
+
"""List monitoring checks"""
|
|
899
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
900
|
+
checks_cmd.list_checks(page, page_size, check_type, status)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
@app.command("get")
|
|
904
|
+
def get_check(
|
|
905
|
+
check_id: str = typer.Argument(..., help="Check ID to retrieve"),
|
|
906
|
+
):
|
|
907
|
+
"""Get specific check details"""
|
|
908
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
909
|
+
checks_cmd.get_check(check_id)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
@app.command("create")
|
|
913
|
+
def create_check(
|
|
914
|
+
name: str = typer.Option(..., "--name", "-n", help="Check name"),
|
|
915
|
+
check_type: str = typer.Option(..., "--type", "-t", help="Check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
916
|
+
url: Optional[str] = typer.Option(None, "--url", "-u", help="URL to monitor (required for web, api, ssl checks)"),
|
|
917
|
+
host: Optional[str] = typer.Option(None, "--host", help="Hostname/IP for TCP/SSL checks (max 255 characters)"),
|
|
918
|
+
port: Optional[int] = typer.Option(None, "--port", help="Port number for TCP checks (1-65535)"),
|
|
919
|
+
interval: int = typer.Option(300, "--interval", "-i", help="Check interval in seconds"),
|
|
920
|
+
timeout: int = typer.Option(30, "--timeout", help="Timeout in seconds"),
|
|
921
|
+
parameters: Optional[str] = typer.Option(None, "--parameters", help="JSON string with check parameters (e.g., '{\"regions\": [\"US\", \"EU\"]}')"),
|
|
922
|
+
pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks")
|
|
923
|
+
):
|
|
924
|
+
"""Create a new monitoring check. Parameters vary by check type:
|
|
925
|
+
- web/api: --url required
|
|
926
|
+
- tcp: --host required, --port optional
|
|
927
|
+
- ssl: --url or --host required
|
|
928
|
+
- synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
|
|
929
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
930
|
+
checks_cmd.create_check(name, check_type, url, host, port, interval, timeout, parameters, pw_script_file)
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
@app.command("update")
|
|
934
|
+
def update_check(
|
|
935
|
+
check_id: str = typer.Argument(..., help="Check ID to update"),
|
|
936
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="New check name"),
|
|
937
|
+
url: Optional[str] = typer.Option(None, "--url", "-u", help="New URL"),
|
|
938
|
+
host: Optional[str] = typer.Option(None, "--host", help="New hostname/IP for TCP/SSL checks (max 255 characters)"),
|
|
939
|
+
port: Optional[int] = typer.Option(None, "--port", help="New port number for TCP checks (1-65535)"),
|
|
940
|
+
interval: Optional[int] = typer.Option(None, "--interval", "-i", help="New interval in seconds"),
|
|
941
|
+
timeout: Optional[int] = typer.Option(None, "--timeout", help="New timeout in seconds"),
|
|
942
|
+
active: bool = typer.Option(None, "--active/--inactive", help="Enable or disable the check"),
|
|
943
|
+
parameters: Optional[str] = typer.Option(None, "--parameters", help="JSON string with check parameters (e.g., '{\"pw_script\": \"...\", \"regions\": [\"US\", \"EU\"]}')"),
|
|
944
|
+
pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks")
|
|
945
|
+
):
|
|
946
|
+
"""Update an existing check. Use --parameters to provide complex parameters like Playwright scripts, regions, etc."""
|
|
947
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
948
|
+
checks_cmd.update_check(check_id, name, url, host, port, interval, timeout, active, parameters, pw_script_file)
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
@app.command("delete")
|
|
952
|
+
def delete_check(
|
|
953
|
+
check_id: str = typer.Argument(..., help="Check ID to delete"),
|
|
954
|
+
confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
|
|
955
|
+
):
|
|
956
|
+
"""Delete a monitoring check"""
|
|
957
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
958
|
+
checks_cmd.delete_check(check_id, confirm)
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
@app.command("results")
|
|
962
|
+
def get_results(
|
|
963
|
+
check_id: Optional[str] = typer.Argument(None, help="Check ID (optional - if not provided, returns all results)"),
|
|
964
|
+
from_date: Optional[str] = typer.Option(None, "--from", help="Start date (ISO 8601) - max 6 months ago"),
|
|
965
|
+
to_date: Optional[str] = typer.Option(None, "--to", help="End date (ISO 8601)"),
|
|
966
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
967
|
+
page_size: int = typer.Option(20, "--page-size", "-s", help="Items per page (1-100)"),
|
|
968
|
+
status: Optional[str] = typer.Option(None, "--status", help="Filter by status (ok, failed, degraded, timeout, pending)"),
|
|
969
|
+
check_type: Optional[str] = typer.Option(None, "--type", help="Filter by check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
970
|
+
region: Optional[str] = typer.Option(None, "--region", help="Filter by region"),
|
|
971
|
+
):
|
|
972
|
+
"""Get check results with advanced filtering. If no check_id is provided, returns unified results across all checks."""
|
|
973
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
974
|
+
checks_cmd.get_check_results(check_id, from_date, to_date, page, page_size, status, check_type, region)
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
@app.command("result")
|
|
978
|
+
def get_result(
|
|
979
|
+
check_id: str = typer.Argument(..., help="Check ID"),
|
|
980
|
+
result_id: str = typer.Argument(..., help="Result ID to retrieve detailed information for"),
|
|
981
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show full detailed information including network requests"),
|
|
982
|
+
):
|
|
983
|
+
"""Get detailed information for a specific check result"""
|
|
984
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
985
|
+
checks_cmd.get_check_result_detailed(check_id, result_id, verbose)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
@app.command("list-regions")
|
|
989
|
+
def list_regions(
|
|
990
|
+
check_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter regions by check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
991
|
+
):
|
|
992
|
+
"""List available regions for monitoring checks"""
|
|
993
|
+
checks_cmd = ChecksCommand(get_output_format())
|
|
994
|
+
checks_cmd.list_regions(check_type)
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
# Import on-demand checks functionality from separate module
|
|
1007
|
+
from .on_demand_checks import run_app, jobs_app
|
|
1008
|
+
|
|
1009
|
+
# Add the run and jobs subcommands to the main checks app
|
|
1010
|
+
app.add_typer(run_app, name="run")
|
|
1011
|
+
app.add_typer(jobs_app, name="jobs")
|