pingera-cli 0.1.11__tar.gz → 0.1.13__tar.gz
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-0.1.11/pingera_cli.egg-info → pingera_cli-0.1.13}/PKG-INFO +2 -2
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/__init__.py +1 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/__main__.py +1 -2
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/auth.py +3 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/check_groups.py +2 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/check_secrets.py +2 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/checks.py +118 -66
- pingera_cli-0.1.13/pingera_cli/commands/execution_groups.py +423 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/on_demand_checks.py +191 -12
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/secrets.py +2 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/__init__.py +3 -1
- pingera_cli-0.1.13/pingera_cli/formatters/dns_formatter.py +66 -0
- pingera_cli-0.1.13/pingera_cli/formatters/icmp_formatter.py +64 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/registry.py +4 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/main.py +11 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13/pingera_cli.egg-info}/PKG-INFO +2 -2
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli.egg-info/SOURCES.txt +3 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli.egg-info/requires.txt +1 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pyproject.toml +1 -1
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/.gitignore +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/.replit +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/LICENSE +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/README.md +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/assert +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/docs/cli-commands.md +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/docs/example-check.yaml +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/docs/example-synthetic-screenshot.json +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/docs/example-web-check.json +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/__init__.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/commands/base.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/base_formatter.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/generic_formatter.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/multistep_formatter.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/ssl_formatter.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/synthetic_formatter.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/formatters/web_formatter.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/utils/__init__.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/utils/config.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/utils/console.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli/utils/file_utils.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli.egg-info/dependency_links.txt +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli.egg-info/entry_points.txt +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/pingera_cli.egg-info/top_level.txt +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/replit.md +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/run_tests.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/setup.cfg +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/setup.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/tests/__init__.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/tests/conftest.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/tests/requirements.txt +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/tests/test_auth.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/tests/test_checks.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/tests/test_config.py +0 -0
- {pingera_cli-0.1.11 → pingera_cli-0.1.13}/tests/test_on_demand_checks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pingera-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13
|
|
4
4
|
Summary: A beautiful Python CLI tool built with typer and rich, distributed via pip and based on Pingera SDK
|
|
5
5
|
Home-page: https://github.com/pingera/pingera-cli
|
|
6
6
|
Author: PingeraCLI Team
|
|
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
|
|
|
31
31
|
License-File: LICENSE
|
|
32
32
|
Requires-Dist: typer[all]>=0.9.0
|
|
33
33
|
Requires-Dist: rich>=13.0.0
|
|
34
|
-
Requires-Dist: pingera-sdk>=1.0.
|
|
34
|
+
Requires-Dist: pingera-sdk>=1.0.11
|
|
35
35
|
Requires-Dist: click>=8.0.0
|
|
36
36
|
Provides-Extra: dev
|
|
37
37
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Pingera CLI - A beautiful CLI tool for Pingera Platform
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "0.1.
|
|
5
|
+
__version__ = "0.1.13"
|
|
6
6
|
__author__ = "Pingera Team"
|
|
7
7
|
__description__ = "A beautiful Python CLI tool built with typer and rich, distributed via pip and based on Pingera SDK"
|
|
8
8
|
|
|
@@ -224,8 +224,10 @@ API Key: ✗ Not set
|
|
|
224
224
|
from pingera.exceptions import UnauthorizedException
|
|
225
225
|
|
|
226
226
|
# Configure the client (same pattern as checks command)
|
|
227
|
+
from ..utils.config import get_config
|
|
228
|
+
|
|
227
229
|
configuration = Configuration()
|
|
228
|
-
configuration.host =
|
|
230
|
+
configuration.host = get_config().get('base_url', 'https://api.pingera.ru')
|
|
229
231
|
configuration.api_key['apiKeyAuth'] = api_key
|
|
230
232
|
|
|
231
233
|
# Create API client
|
|
@@ -33,10 +33,11 @@ class CheckGroupsCommand(BaseCommand):
|
|
|
33
33
|
try:
|
|
34
34
|
from pingera import ApiClient, Configuration
|
|
35
35
|
from pingera.api import CheckGroupsApi
|
|
36
|
+
from ..utils.config import get_config
|
|
36
37
|
|
|
37
38
|
# Configure the client
|
|
38
39
|
configuration = Configuration()
|
|
39
|
-
configuration.host =
|
|
40
|
+
configuration.host = get_config().get('base_url', 'https://api.pingera.ru')
|
|
40
41
|
configuration.api_key['apiKeyAuth'] = api_key
|
|
41
42
|
|
|
42
43
|
# Create API client
|
|
@@ -34,10 +34,11 @@ class CheckSecretsCommand(BaseCommand):
|
|
|
34
34
|
try:
|
|
35
35
|
from pingera import ApiClient, Configuration
|
|
36
36
|
from pingera.api import CheckSecretsApi
|
|
37
|
+
from ..utils.config import get_config
|
|
37
38
|
|
|
38
39
|
# Configure the client
|
|
39
40
|
configuration = Configuration()
|
|
40
|
-
configuration.host =
|
|
41
|
+
configuration.host = get_config().get('base_url', 'https://api.pingera.ru')
|
|
41
42
|
configuration.api_key['apiKeyAuth'] = api_key
|
|
42
43
|
|
|
43
44
|
# Create API client
|
|
@@ -33,10 +33,11 @@ class ChecksCommand(BaseCommand):
|
|
|
33
33
|
try:
|
|
34
34
|
from pingera import ApiClient, Configuration
|
|
35
35
|
from pingera.api import ChecksApi
|
|
36
|
+
from ..utils.config import get_config
|
|
36
37
|
|
|
37
38
|
# Configure the client
|
|
38
39
|
configuration = Configuration()
|
|
39
|
-
configuration.host =
|
|
40
|
+
configuration.host = get_config().get('base_url', 'https://api.pingera.ru')
|
|
40
41
|
configuration.api_key['apiKeyAuth'] = api_key
|
|
41
42
|
|
|
42
43
|
# Create API client
|
|
@@ -59,10 +60,11 @@ class ChecksCommand(BaseCommand):
|
|
|
59
60
|
try:
|
|
60
61
|
from pingera import ApiClient, Configuration
|
|
61
62
|
from pingera.api import ChecksUnifiedResultsApi
|
|
63
|
+
from ..utils.config import get_config
|
|
62
64
|
|
|
63
65
|
# Configure the client
|
|
64
66
|
configuration = Configuration()
|
|
65
|
-
configuration.host =
|
|
67
|
+
configuration.host = get_config().get('base_url', 'https://api.pingera.ru')
|
|
66
68
|
configuration.api_key['apiKeyAuth'] = api_key
|
|
67
69
|
|
|
68
70
|
# Create API client
|
|
@@ -85,7 +87,7 @@ class ChecksCommand(BaseCommand):
|
|
|
85
87
|
"page": page,
|
|
86
88
|
"page_size": page_size
|
|
87
89
|
}
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
# Add optional filters
|
|
90
92
|
if check_type:
|
|
91
93
|
params["type"] = check_type
|
|
@@ -148,12 +150,12 @@ class ChecksCommand(BaseCommand):
|
|
|
148
150
|
filters_applied.append(f"name='{name}'")
|
|
149
151
|
if group_id:
|
|
150
152
|
filters_applied.append(f"group={group_id}")
|
|
151
|
-
|
|
153
|
+
|
|
152
154
|
if filters_applied:
|
|
153
155
|
title_parts.append(f"({', '.join(filters_applied)})")
|
|
154
|
-
|
|
156
|
+
|
|
155
157
|
table_title = " ".join(title_parts)
|
|
156
|
-
|
|
158
|
+
|
|
157
159
|
# Create table for default output
|
|
158
160
|
table = Table(title=table_title)
|
|
159
161
|
table.add_column("ID", style="cyan")
|
|
@@ -181,7 +183,7 @@ class ChecksCommand(BaseCommand):
|
|
|
181
183
|
group_display = group_name[:19] + "…"
|
|
182
184
|
else:
|
|
183
185
|
group_display = group_name
|
|
184
|
-
|
|
186
|
+
|
|
185
187
|
# Add color if available
|
|
186
188
|
if hasattr(check.group, 'color') and check.group.color:
|
|
187
189
|
# Use the group color for the group name display
|
|
@@ -194,8 +196,15 @@ class ChecksCommand(BaseCommand):
|
|
|
194
196
|
check_url = "-"
|
|
195
197
|
if hasattr(check, 'url') and check.url:
|
|
196
198
|
check_url = str(check.url)
|
|
197
|
-
elif hasattr(check, 'host') and
|
|
198
|
-
|
|
199
|
+
elif hasattr(check, 'host') and check.host:
|
|
200
|
+
# For ICMP and DNS checks, show just the host
|
|
201
|
+
# For TCP and other checks with ports, show host:port
|
|
202
|
+
if check_type in ["icmp", "dns"]:
|
|
203
|
+
check_url = str(check.host)
|
|
204
|
+
elif hasattr(check, 'port') and check.port:
|
|
205
|
+
check_url = f"{check.host}:{check.port}"
|
|
206
|
+
else:
|
|
207
|
+
check_url = str(check.host)
|
|
199
208
|
|
|
200
209
|
# Handle active status
|
|
201
210
|
active_status = "✅" if hasattr(check, 'active') and check.active else "❌"
|
|
@@ -227,16 +236,16 @@ class ChecksCommand(BaseCommand):
|
|
|
227
236
|
)
|
|
228
237
|
|
|
229
238
|
self.console.print(table)
|
|
230
|
-
|
|
239
|
+
|
|
231
240
|
# Show result summary with filter info
|
|
232
241
|
summary_parts = [f"Found {len(response.checks)} checks"]
|
|
233
242
|
if page > 1:
|
|
234
243
|
summary_parts.append(f"on page {page}")
|
|
235
244
|
if filters_applied:
|
|
236
245
|
summary_parts.append(f"with filters: {', '.join(filters_applied)}")
|
|
237
|
-
|
|
246
|
+
|
|
238
247
|
self.console.print(f"\n[dim]{' '.join(summary_parts)}[/dim]")
|
|
239
|
-
|
|
248
|
+
|
|
240
249
|
# Show helpful hints for filtering
|
|
241
250
|
if not any([check_type, status, name, group_id]):
|
|
242
251
|
self.console.print(f"[dim]💡 Filter checks: --type <type>, --status <status>, --name <name>, --group-id <id>[/dim]")
|
|
@@ -293,7 +302,7 @@ class ChecksCommand(BaseCommand):
|
|
|
293
302
|
group_name = check.group.name if hasattr(check.group, 'name') else 'Unknown'
|
|
294
303
|
group_color = check.group.color if hasattr(check.group, 'color') and check.group.color else 'white'
|
|
295
304
|
group_desc = check.group.description if hasattr(check.group, 'description') and check.group.description else ''
|
|
296
|
-
|
|
305
|
+
|
|
297
306
|
group_info = f"• Group: [{group_color}]● {group_name}[/{group_color}]"
|
|
298
307
|
if group_desc:
|
|
299
308
|
group_info += f" [dim]({group_desc})[/dim]"
|
|
@@ -392,13 +401,13 @@ class ChecksCommand(BaseCommand):
|
|
|
392
401
|
def _parse_check_file(self, file_path: str) -> dict:
|
|
393
402
|
"""Parse check configuration from JSON or YAML file (local or URL)"""
|
|
394
403
|
from ..utils.file_utils import load_check_file, is_url
|
|
395
|
-
|
|
404
|
+
|
|
396
405
|
try:
|
|
397
406
|
if is_url(file_path):
|
|
398
407
|
self.display_info(f"Downloading check configuration from: {file_path}")
|
|
399
|
-
|
|
408
|
+
|
|
400
409
|
return load_check_file(file_path)
|
|
401
|
-
|
|
410
|
+
|
|
402
411
|
except Exception as e:
|
|
403
412
|
self.display_error(str(e))
|
|
404
413
|
raise typer.Exit(1)
|
|
@@ -413,12 +422,12 @@ class ChecksCommand(BaseCommand):
|
|
|
413
422
|
# If creating from file, parse file and merge with command line options
|
|
414
423
|
if from_file:
|
|
415
424
|
file_data = self._parse_check_file(from_file)
|
|
416
|
-
|
|
425
|
+
|
|
417
426
|
# Filter only SDK-recognized fields from file data
|
|
418
427
|
# This ignores marketplace-specific fields and other extensions
|
|
419
428
|
sdk_fields = ["name", "type", "url", "host", "port", "interval", "timeout", "parameters", "active", "secrets"]
|
|
420
429
|
filtered_file_data = {k: v for k, v in file_data.items() if k in sdk_fields}
|
|
421
|
-
|
|
430
|
+
|
|
422
431
|
# Command line options take precedence over file data
|
|
423
432
|
check_data = {
|
|
424
433
|
"name": filtered_file_data.get("name", name),
|
|
@@ -426,7 +435,7 @@ class ChecksCommand(BaseCommand):
|
|
|
426
435
|
"interval": filtered_file_data.get("interval", interval),
|
|
427
436
|
"timeout": filtered_file_data.get("timeout", timeout)
|
|
428
437
|
}
|
|
429
|
-
|
|
438
|
+
|
|
430
439
|
# Override with command line values if provided (but only if they were explicitly changed from defaults)
|
|
431
440
|
# We need to be more careful about when to override file values
|
|
432
441
|
# For now, just use file values and let user override with explicit CLI options
|
|
@@ -434,46 +443,46 @@ class ChecksCommand(BaseCommand):
|
|
|
434
443
|
check_data["interval"] = interval
|
|
435
444
|
if timeout != 30: # Only override if timeout was explicitly provided
|
|
436
445
|
check_data["timeout"] = timeout
|
|
437
|
-
|
|
446
|
+
|
|
438
447
|
# Handle URL from file
|
|
439
448
|
if url is None and filtered_file_data.get("url"):
|
|
440
449
|
url = filtered_file_data["url"]
|
|
441
450
|
elif url is not None:
|
|
442
451
|
# Command line URL takes precedence
|
|
443
452
|
pass
|
|
444
|
-
|
|
453
|
+
|
|
445
454
|
# Handle host/port from file
|
|
446
455
|
if host is None and filtered_file_data.get("host"):
|
|
447
456
|
host = filtered_file_data["host"]
|
|
448
457
|
elif host is not None:
|
|
449
458
|
# Command line host takes precedence
|
|
450
459
|
pass
|
|
451
|
-
|
|
460
|
+
|
|
452
461
|
if port is None and filtered_file_data.get("port"):
|
|
453
462
|
port = filtered_file_data["port"]
|
|
454
463
|
elif port is not None:
|
|
455
464
|
# Command line port takes precedence
|
|
456
465
|
pass
|
|
457
|
-
|
|
466
|
+
|
|
458
467
|
# Handle parameters from file
|
|
459
468
|
file_parameters = filtered_file_data.get("parameters")
|
|
460
469
|
if file_parameters and parameters is None:
|
|
461
470
|
# Use file parameters if no command line parameters
|
|
462
471
|
parameters = json.dumps(file_parameters) if isinstance(file_parameters, dict) else file_parameters
|
|
463
|
-
|
|
472
|
+
|
|
464
473
|
# Handle secrets from file
|
|
465
474
|
if filtered_file_data.get("secrets"):
|
|
466
475
|
check_data["secrets"] = filtered_file_data["secrets"]
|
|
467
|
-
|
|
476
|
+
|
|
468
477
|
# Handle pw_script_file from file (this is CLI-specific, not sent to SDK)
|
|
469
478
|
if pw_script_file is None and file_data.get("pw_script_file"):
|
|
470
479
|
pw_script_file = file_data["pw_script_file"]
|
|
471
|
-
|
|
480
|
+
|
|
472
481
|
# Show info about ignored fields if any
|
|
473
482
|
ignored_fields = [k for k in file_data.keys() if k not in sdk_fields and k != "pw_script_file"]
|
|
474
483
|
if ignored_fields:
|
|
475
484
|
self.display_info(f"Ignoring non-SDK fields from file: {', '.join(ignored_fields)}")
|
|
476
|
-
|
|
485
|
+
|
|
477
486
|
self.display_info(f"Creating check from file: {from_file}")
|
|
478
487
|
else:
|
|
479
488
|
# Build check data normally
|
|
@@ -537,7 +546,7 @@ class ChecksCommand(BaseCommand):
|
|
|
537
546
|
self.display_error(f"URL is required for {actual_check_type} checks")
|
|
538
547
|
raise typer.Exit(1)
|
|
539
548
|
|
|
540
|
-
if actual_check_type in ['tcp'] and not host:
|
|
549
|
+
if actual_check_type in ['tcp', 'icmp', 'dns'] and not host:
|
|
541
550
|
self.display_error(f"Host is required for {actual_check_type} checks")
|
|
542
551
|
raise typer.Exit(1)
|
|
543
552
|
|
|
@@ -696,7 +705,7 @@ class ChecksCommand(BaseCommand):
|
|
|
696
705
|
self.display_error(f"Failed to delete check: {str(e)}")
|
|
697
706
|
raise typer.Exit(1)
|
|
698
707
|
|
|
699
|
-
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):
|
|
708
|
+
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, result_id: Optional[str] = None):
|
|
700
709
|
"""Get check results using unified results API"""
|
|
701
710
|
try:
|
|
702
711
|
unified_api = self.get_unified_results_client()
|
|
@@ -711,6 +720,10 @@ class ChecksCommand(BaseCommand):
|
|
|
711
720
|
if check_id:
|
|
712
721
|
params["check_id"] = check_id
|
|
713
722
|
|
|
723
|
+
# Add result_id if provided (for fetching specific result)
|
|
724
|
+
if result_id:
|
|
725
|
+
params["result_id"] = result_id
|
|
726
|
+
|
|
714
727
|
# Add optional filters
|
|
715
728
|
if from_date:
|
|
716
729
|
params["start_date"] = from_date
|
|
@@ -733,6 +746,36 @@ class ChecksCommand(BaseCommand):
|
|
|
733
746
|
self.display_info("No results found.")
|
|
734
747
|
return
|
|
735
748
|
|
|
749
|
+
# If result_id was specified, we're fetching a single result - display it with full formatting
|
|
750
|
+
if result_id and len(response.results) == 1:
|
|
751
|
+
result = response.results[0]
|
|
752
|
+
|
|
753
|
+
if self.output_format in ['json', 'yaml']:
|
|
754
|
+
result_dict = {
|
|
755
|
+
"id": str(result.id) if hasattr(result, 'id') and result.id else None,
|
|
756
|
+
"check_id": result.check_id if hasattr(result, 'check_id') else None,
|
|
757
|
+
"check_name": result.check_name if hasattr(result, 'check_name') else None,
|
|
758
|
+
"check_type": result.check_type if hasattr(result, 'check_type') else None,
|
|
759
|
+
"status": result.status if hasattr(result, 'status') else None,
|
|
760
|
+
"created_at": result.created_at if hasattr(result, 'created_at') and result.created_at else None,
|
|
761
|
+
"response_time": result.response_time if hasattr(result, 'response_time') else None,
|
|
762
|
+
"error_message": result.error_message if hasattr(result, 'error_message') else None,
|
|
763
|
+
"check_server_id": result.check_server_id if hasattr(result, 'check_server_id') else None,
|
|
764
|
+
"region": result.region if hasattr(result, 'region') else None,
|
|
765
|
+
"result_type": result.result_type if hasattr(result, 'result_type') else None,
|
|
766
|
+
"check_metadata": result.check_metadata if hasattr(result, 'check_metadata') else None,
|
|
767
|
+
"check_server": {
|
|
768
|
+
"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,
|
|
769
|
+
"country": result.check_server.country if hasattr(result, 'check_server') and result.check_server and hasattr(result.check_server, 'country') else None,
|
|
770
|
+
"region": result.check_server.region if hasattr(result, 'check_server') and result.check_server and hasattr(result.check_server, 'region') else None
|
|
771
|
+
} if hasattr(result, 'check_server') and result.check_server else None
|
|
772
|
+
}
|
|
773
|
+
self.output_data(result_dict)
|
|
774
|
+
else:
|
|
775
|
+
# Display with full formatting using _display_detailed_result
|
|
776
|
+
self._display_detailed_result(result, verbose=True)
|
|
777
|
+
return
|
|
778
|
+
|
|
736
779
|
# Prepare data for different output formats
|
|
737
780
|
if self.output_format in ['json', 'yaml']:
|
|
738
781
|
results_data = []
|
|
@@ -861,8 +904,7 @@ class ChecksCommand(BaseCommand):
|
|
|
861
904
|
if check_id:
|
|
862
905
|
hints.append(f"Use 'pngr checks result {check_id} <result-id>' for detailed result info")
|
|
863
906
|
else:
|
|
864
|
-
hints.append("Use 'pngr checks result
|
|
865
|
-
hints.append("For on-demand checks: use 'pngr checks jobs result <result-id>' for detailed info")
|
|
907
|
+
hints.append("Use 'pngr checks results --result-id <result-id>' for detailed result info")
|
|
866
908
|
hints.append("Use 'pngr checks list' to see all available checks and their IDs")
|
|
867
909
|
hints.append("Filter results: --type <type>, --status <status>, --region <region>")
|
|
868
910
|
|
|
@@ -929,7 +971,15 @@ class ChecksCommand(BaseCommand):
|
|
|
929
971
|
"""Display detailed result information in a rich format"""
|
|
930
972
|
|
|
931
973
|
# Basic information
|
|
932
|
-
|
|
974
|
+
# Handle response time display: 0 means <1ms, None/missing means N/A
|
|
975
|
+
if hasattr(result, 'response_time') and result.response_time is not None:
|
|
976
|
+
if result.response_time == 0:
|
|
977
|
+
response_time_display = "<1ms"
|
|
978
|
+
else:
|
|
979
|
+
response_time_display = f"{result.response_time}ms"
|
|
980
|
+
else:
|
|
981
|
+
response_time_display = 'N/A'
|
|
982
|
+
|
|
933
983
|
basic_info = f"""[bold cyan]Basic Information:[/bold cyan]
|
|
934
984
|
• Result ID: [white]{result.id}[/white]
|
|
935
985
|
• Check ID: [white]{result.check_id}[/white]
|
|
@@ -1029,7 +1079,7 @@ class ChecksCommand(BaseCommand):
|
|
|
1029
1079
|
# Extract region data using actual SDK attributes
|
|
1030
1080
|
region_id = str(region.id) if hasattr(region, 'id') and region.id else "-"
|
|
1031
1081
|
display_name = region.display_name if hasattr(region, 'display_name') and region.display_name else "-"
|
|
1032
|
-
|
|
1082
|
+
|
|
1033
1083
|
# Handle available check types
|
|
1034
1084
|
available_types = "-"
|
|
1035
1085
|
if hasattr(region, 'available_check_types') and region.available_check_types:
|
|
@@ -1045,7 +1095,7 @@ class ChecksCommand(BaseCommand):
|
|
|
1045
1095
|
)
|
|
1046
1096
|
|
|
1047
1097
|
self.console.print(table)
|
|
1048
|
-
|
|
1098
|
+
|
|
1049
1099
|
# Show summary
|
|
1050
1100
|
filter_text = f" for {check_type} checks" if check_type else ""
|
|
1051
1101
|
self.console.print(f"\n[dim]Found {len(response.regions)} regions{filter_text}[/dim]")
|
|
@@ -1108,32 +1158,28 @@ class ChecksCommand(BaseCommand):
|
|
|
1108
1158
|
app = typer.Typer(name="checks", help="🔍 Manage monitoring checks", no_args_is_help=True)
|
|
1109
1159
|
|
|
1110
1160
|
|
|
1111
|
-
def get_output_format():
|
|
1112
|
-
"""Get output format from config"""
|
|
1113
|
-
from ..utils.config import get_config
|
|
1114
|
-
return get_config().get('output_format', 'table')
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
1161
|
@app.command("list")
|
|
1118
1162
|
def list_checks(
|
|
1119
1163
|
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
1120
|
-
page_size: int = typer.Option(20, "--page-size", "-s", help="Items per page
|
|
1121
|
-
check_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by check type (web, api, ssl, tcp, synthetic, multistep)"),
|
|
1164
|
+
page_size: int = typer.Option(20, "--page-size", "-s", help="Items per page"),
|
|
1165
|
+
check_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by check type (web, api, ssl, tcp, icmp, dns, synthetic, multistep)"),
|
|
1122
1166
|
status: Optional[str] = typer.Option(None, "--status", help="Filter by status. Multiple statuses can be separated by commas (e.g., 'ok,failed')"),
|
|
1123
1167
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name using case-insensitive partial matching (max 100 chars)"),
|
|
1124
1168
|
group_id: Optional[str] = typer.Option(None, "--group-id", "-g", help="Filter by group ID"),
|
|
1125
1169
|
):
|
|
1126
1170
|
"""List monitoring checks with advanced filtering options"""
|
|
1171
|
+
from ..utils.config import get_output_format
|
|
1172
|
+
|
|
1127
1173
|
# Validate name length
|
|
1128
1174
|
if name and len(name) > 100:
|
|
1129
1175
|
typer.echo("Error: Name filter cannot exceed 100 characters", err=True)
|
|
1130
1176
|
raise typer.Exit(1)
|
|
1131
|
-
|
|
1177
|
+
|
|
1132
1178
|
# Validate check type
|
|
1133
|
-
if check_type and check_type not in ["web", "api", "ssl", "tcp", "synthetic", "multistep"]:
|
|
1134
|
-
typer.echo("Error: Invalid check type. Must be one of: web, api, ssl, tcp, synthetic, multistep", err=True)
|
|
1179
|
+
if check_type and check_type not in ["web", "api", "ssl", "tcp", "icmp", "dns", "synthetic", "multistep"]:
|
|
1180
|
+
typer.echo("Error: Invalid check type. Must be one of: web, api, ssl, tcp, icmp, dns, synthetic, multistep", err=True)
|
|
1135
1181
|
raise typer.Exit(1)
|
|
1136
|
-
|
|
1182
|
+
|
|
1137
1183
|
# Validate status values if provided
|
|
1138
1184
|
if status:
|
|
1139
1185
|
valid_statuses = ["ok", "failed", "degraded", "timeout", "pending", "paused"]
|
|
@@ -1142,7 +1188,7 @@ def list_checks(
|
|
|
1142
1188
|
if s not in valid_statuses:
|
|
1143
1189
|
typer.echo(f"Error: Invalid status '{s}'. Must be one of: {', '.join(valid_statuses)}", err=True)
|
|
1144
1190
|
raise typer.Exit(1)
|
|
1145
|
-
|
|
1191
|
+
|
|
1146
1192
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1147
1193
|
checks_cmd.list_checks(page, page_size, check_type, status, name, group_id)
|
|
1148
1194
|
|
|
@@ -1152,6 +1198,7 @@ def get_check(
|
|
|
1152
1198
|
check_id: str = typer.Argument(..., help="Check ID to retrieve"),
|
|
1153
1199
|
):
|
|
1154
1200
|
"""Get specific check details"""
|
|
1201
|
+
from ..utils.config import get_output_format
|
|
1155
1202
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1156
1203
|
checks_cmd.get_check(check_id)
|
|
1157
1204
|
|
|
@@ -1159,9 +1206,9 @@ def get_check(
|
|
|
1159
1206
|
@app.command("create")
|
|
1160
1207
|
def create_check(
|
|
1161
1208
|
name: str = typer.Option("", "--name", "-n", help="Check name (required if not using --from-file)"),
|
|
1162
|
-
check_type: str = typer.Option("web", "--type", "-t", help="Check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
1209
|
+
check_type: str = typer.Option("web", "--type", "-t", help="Check type (web, api, tcp, ssl, icmp, dns, synthetic, multistep)"),
|
|
1163
1210
|
url: Optional[str] = typer.Option(None, "--url", "-u", help="URL to monitor (required for web, api, ssl checks)"),
|
|
1164
|
-
host: Optional[str] = typer.Option(None, "--host", help="Hostname/IP for TCP/SSL checks (max 255 characters)"),
|
|
1211
|
+
host: Optional[str] = typer.Option(None, "--host", help="Hostname/IP for TCP/SSL/ICMP/DNS checks (max 255 characters)"),
|
|
1165
1212
|
port: Optional[int] = typer.Option(None, "--port", help="Port number for TCP checks (1-65535)"),
|
|
1166
1213
|
interval: int = typer.Option(300, "--interval", "-i", help="Check interval in seconds"),
|
|
1167
1214
|
timeout: int = typer.Option(30, "--timeout", help="Timeout in seconds"),
|
|
@@ -1170,22 +1217,25 @@ def create_check(
|
|
|
1170
1217
|
from_file: Optional[str] = typer.Option(None, "--from-file", "-f", help="Path to JSON or YAML file containing check configuration")
|
|
1171
1218
|
):
|
|
1172
1219
|
"""Create a new monitoring check. Can be created from command line options or from a JSON/YAML file.
|
|
1173
|
-
|
|
1220
|
+
|
|
1174
1221
|
When using --from-file:
|
|
1175
1222
|
- Command line options override file values
|
|
1176
1223
|
- File should contain check configuration in JSON or YAML format
|
|
1177
|
-
|
|
1224
|
+
|
|
1178
1225
|
Parameters vary by check type:
|
|
1179
1226
|
- web/api: --url required
|
|
1180
1227
|
- tcp: --host required, --port optional
|
|
1181
1228
|
- ssl: --url or --host required
|
|
1229
|
+
- icmp: --host required (hostname or IP to ping)
|
|
1230
|
+
- dns: --host required (domain name to query)
|
|
1182
1231
|
- synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
|
|
1183
|
-
|
|
1232
|
+
from ..utils.config import get_output_format
|
|
1233
|
+
|
|
1184
1234
|
# Validate that we have either from_file or required parameters
|
|
1185
1235
|
if not from_file and not name:
|
|
1186
1236
|
typer.echo("Error: --name is required when not using --from-file", err=True)
|
|
1187
1237
|
raise typer.Exit(1)
|
|
1188
|
-
|
|
1238
|
+
|
|
1189
1239
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1190
1240
|
checks_cmd.create_check(name, check_type, url, host, port, interval, timeout, parameters, pw_script_file, from_file)
|
|
1191
1241
|
|
|
@@ -1204,6 +1254,7 @@ def update_check(
|
|
|
1204
1254
|
pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks")
|
|
1205
1255
|
):
|
|
1206
1256
|
"""Update an existing check. Use --parameters to provide complex parameters like Playwright scripts, regions, etc."""
|
|
1257
|
+
from ..utils.config import get_output_format
|
|
1207
1258
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1208
1259
|
checks_cmd.update_check(check_id, name, url, host, port, interval, timeout, active, parameters, pw_script_file)
|
|
1209
1260
|
|
|
@@ -1214,6 +1265,7 @@ def delete_check(
|
|
|
1214
1265
|
confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
|
|
1215
1266
|
):
|
|
1216
1267
|
"""Delete a monitoring check"""
|
|
1268
|
+
from ..utils.config import get_output_format
|
|
1217
1269
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1218
1270
|
checks_cmd.delete_check(check_id, confirm)
|
|
1219
1271
|
|
|
@@ -1228,10 +1280,12 @@ def get_results(
|
|
|
1228
1280
|
status: Optional[str] = typer.Option(None, "--status", help="Filter by status (ok, failed, degraded, timeout, pending)"),
|
|
1229
1281
|
check_type: Optional[str] = typer.Option(None, "--type", help="Filter by check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
1230
1282
|
region: Optional[str] = typer.Option(None, "--region", help="Filter by region"),
|
|
1283
|
+
result_id: Optional[str] = typer.Option(None, "--result-id", "-r", help="Filter by specific result ID"),
|
|
1231
1284
|
):
|
|
1232
|
-
"""Get check results with advanced filtering. If no check_id is provided, returns unified results across all checks."""
|
|
1285
|
+
"""Get check results with advanced filtering. If no check_id is provided, returns unified results across all checks. Use --result-id to fetch a specific result."""
|
|
1286
|
+
from ..utils.config import get_output_format
|
|
1233
1287
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1234
|
-
checks_cmd.get_check_results(check_id, from_date, to_date, page, page_size, status, check_type, region)
|
|
1288
|
+
checks_cmd.get_check_results(check_id, from_date, to_date, page, page_size, status, check_type, region, result_id)
|
|
1235
1289
|
|
|
1236
1290
|
|
|
1237
1291
|
@app.command("result")
|
|
@@ -1241,6 +1295,7 @@ def get_result(
|
|
|
1241
1295
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show full detailed information including network requests"),
|
|
1242
1296
|
):
|
|
1243
1297
|
"""Get detailed information for a specific check result"""
|
|
1298
|
+
from ..utils.config import get_output_format
|
|
1244
1299
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1245
1300
|
checks_cmd.get_check_result_detailed(check_id, result_id, verbose)
|
|
1246
1301
|
|
|
@@ -1250,6 +1305,7 @@ def list_regions(
|
|
|
1250
1305
|
check_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter regions by check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
1251
1306
|
):
|
|
1252
1307
|
"""List available regions for monitoring checks"""
|
|
1308
|
+
from ..utils.config import get_output_format
|
|
1253
1309
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1254
1310
|
checks_cmd.list_regions(check_type)
|
|
1255
1311
|
|
|
@@ -1260,19 +1316,11 @@ def assign_check_to_group(
|
|
|
1260
1316
|
group_id: Optional[str] = typer.Option(None, "--group-id", "-g", help="Group ID to assign check to (use 'null' to remove from group)"),
|
|
1261
1317
|
):
|
|
1262
1318
|
"""Assign a check to a group or remove it from a group"""
|
|
1319
|
+
from ..utils.config import get_output_format
|
|
1263
1320
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1264
1321
|
checks_cmd.assign_check_to_group(check_id, group_id)
|
|
1265
1322
|
|
|
1266
1323
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
1324
|
# Import on-demand checks functionality from separate module
|
|
1277
1325
|
from .on_demand_checks import run_app, jobs_app
|
|
1278
1326
|
|
|
@@ -1282,8 +1330,12 @@ from .check_groups import app as groups_app
|
|
|
1282
1330
|
# Import check secrets functionality
|
|
1283
1331
|
from .check_secrets import app as check_secrets_app
|
|
1284
1332
|
|
|
1285
|
-
#
|
|
1333
|
+
# Import execution groups functionality
|
|
1334
|
+
from .execution_groups import app as execution_groups_app
|
|
1335
|
+
|
|
1336
|
+
# Add the run, jobs, groups, secrets, and execution-groups subcommands to the main checks app
|
|
1286
1337
|
app.add_typer(run_app, name="run")
|
|
1287
1338
|
app.add_typer(jobs_app, name="jobs")
|
|
1288
1339
|
app.add_typer(groups_app, name="groups")
|
|
1289
|
-
app.add_typer(check_secrets_app, name="secrets")
|
|
1340
|
+
app.add_typer(check_secrets_app, name="secrets")
|
|
1341
|
+
app.add_typer(execution_groups_app, name="execution-groups")
|