pingera-cli 0.1.12__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.12/pingera_cli.egg-info → pingera_cli-0.1.13}/PKG-INFO +2 -2
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/__init__.py +1 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/__main__.py +1 -2
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/auth.py +3 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/check_groups.py +2 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/check_secrets.py +2 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/checks.py +101 -58
- pingera_cli-0.1.13/pingera_cli/commands/execution_groups.py +423 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/on_demand_checks.py +191 -12
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/secrets.py +2 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/main.py +11 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13/pingera_cli.egg-info}/PKG-INFO +2 -2
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli.egg-info/SOURCES.txt +1 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli.egg-info/requires.txt +1 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pyproject.toml +1 -1
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/.gitignore +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/.replit +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/LICENSE +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/README.md +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/assert +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/docs/cli-commands.md +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/docs/example-check.yaml +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/docs/example-synthetic-screenshot.json +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/docs/example-web-check.json +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/__init__.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/commands/base.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/__init__.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/base_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/dns_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/generic_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/icmp_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/multistep_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/registry.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/ssl_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/synthetic_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/formatters/web_formatter.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/utils/__init__.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/utils/config.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/utils/console.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli/utils/file_utils.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli.egg-info/dependency_links.txt +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli.egg-info/entry_points.txt +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/pingera_cli.egg-info/top_level.txt +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/replit.md +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/run_tests.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/setup.cfg +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/setup.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/tests/__init__.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/tests/conftest.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/tests/requirements.txt +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/tests/test_auth.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/tests/test_checks.py +0 -0
- {pingera_cli-0.1.12 → pingera_cli-0.1.13}/tests/test_config.py +0 -0
- {pingera_cli-0.1.12 → 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
|
|
@@ -234,16 +236,16 @@ class ChecksCommand(BaseCommand):
|
|
|
234
236
|
)
|
|
235
237
|
|
|
236
238
|
self.console.print(table)
|
|
237
|
-
|
|
239
|
+
|
|
238
240
|
# Show result summary with filter info
|
|
239
241
|
summary_parts = [f"Found {len(response.checks)} checks"]
|
|
240
242
|
if page > 1:
|
|
241
243
|
summary_parts.append(f"on page {page}")
|
|
242
244
|
if filters_applied:
|
|
243
245
|
summary_parts.append(f"with filters: {', '.join(filters_applied)}")
|
|
244
|
-
|
|
246
|
+
|
|
245
247
|
self.console.print(f"\n[dim]{' '.join(summary_parts)}[/dim]")
|
|
246
|
-
|
|
248
|
+
|
|
247
249
|
# Show helpful hints for filtering
|
|
248
250
|
if not any([check_type, status, name, group_id]):
|
|
249
251
|
self.console.print(f"[dim]💡 Filter checks: --type <type>, --status <status>, --name <name>, --group-id <id>[/dim]")
|
|
@@ -300,7 +302,7 @@ class ChecksCommand(BaseCommand):
|
|
|
300
302
|
group_name = check.group.name if hasattr(check.group, 'name') else 'Unknown'
|
|
301
303
|
group_color = check.group.color if hasattr(check.group, 'color') and check.group.color else 'white'
|
|
302
304
|
group_desc = check.group.description if hasattr(check.group, 'description') and check.group.description else ''
|
|
303
|
-
|
|
305
|
+
|
|
304
306
|
group_info = f"• Group: [{group_color}]● {group_name}[/{group_color}]"
|
|
305
307
|
if group_desc:
|
|
306
308
|
group_info += f" [dim]({group_desc})[/dim]"
|
|
@@ -399,13 +401,13 @@ class ChecksCommand(BaseCommand):
|
|
|
399
401
|
def _parse_check_file(self, file_path: str) -> dict:
|
|
400
402
|
"""Parse check configuration from JSON or YAML file (local or URL)"""
|
|
401
403
|
from ..utils.file_utils import load_check_file, is_url
|
|
402
|
-
|
|
404
|
+
|
|
403
405
|
try:
|
|
404
406
|
if is_url(file_path):
|
|
405
407
|
self.display_info(f"Downloading check configuration from: {file_path}")
|
|
406
|
-
|
|
408
|
+
|
|
407
409
|
return load_check_file(file_path)
|
|
408
|
-
|
|
410
|
+
|
|
409
411
|
except Exception as e:
|
|
410
412
|
self.display_error(str(e))
|
|
411
413
|
raise typer.Exit(1)
|
|
@@ -420,12 +422,12 @@ class ChecksCommand(BaseCommand):
|
|
|
420
422
|
# If creating from file, parse file and merge with command line options
|
|
421
423
|
if from_file:
|
|
422
424
|
file_data = self._parse_check_file(from_file)
|
|
423
|
-
|
|
425
|
+
|
|
424
426
|
# Filter only SDK-recognized fields from file data
|
|
425
427
|
# This ignores marketplace-specific fields and other extensions
|
|
426
428
|
sdk_fields = ["name", "type", "url", "host", "port", "interval", "timeout", "parameters", "active", "secrets"]
|
|
427
429
|
filtered_file_data = {k: v for k, v in file_data.items() if k in sdk_fields}
|
|
428
|
-
|
|
430
|
+
|
|
429
431
|
# Command line options take precedence over file data
|
|
430
432
|
check_data = {
|
|
431
433
|
"name": filtered_file_data.get("name", name),
|
|
@@ -433,7 +435,7 @@ class ChecksCommand(BaseCommand):
|
|
|
433
435
|
"interval": filtered_file_data.get("interval", interval),
|
|
434
436
|
"timeout": filtered_file_data.get("timeout", timeout)
|
|
435
437
|
}
|
|
436
|
-
|
|
438
|
+
|
|
437
439
|
# Override with command line values if provided (but only if they were explicitly changed from defaults)
|
|
438
440
|
# We need to be more careful about when to override file values
|
|
439
441
|
# For now, just use file values and let user override with explicit CLI options
|
|
@@ -441,46 +443,46 @@ class ChecksCommand(BaseCommand):
|
|
|
441
443
|
check_data["interval"] = interval
|
|
442
444
|
if timeout != 30: # Only override if timeout was explicitly provided
|
|
443
445
|
check_data["timeout"] = timeout
|
|
444
|
-
|
|
446
|
+
|
|
445
447
|
# Handle URL from file
|
|
446
448
|
if url is None and filtered_file_data.get("url"):
|
|
447
449
|
url = filtered_file_data["url"]
|
|
448
450
|
elif url is not None:
|
|
449
451
|
# Command line URL takes precedence
|
|
450
452
|
pass
|
|
451
|
-
|
|
453
|
+
|
|
452
454
|
# Handle host/port from file
|
|
453
455
|
if host is None and filtered_file_data.get("host"):
|
|
454
456
|
host = filtered_file_data["host"]
|
|
455
457
|
elif host is not None:
|
|
456
458
|
# Command line host takes precedence
|
|
457
459
|
pass
|
|
458
|
-
|
|
460
|
+
|
|
459
461
|
if port is None and filtered_file_data.get("port"):
|
|
460
462
|
port = filtered_file_data["port"]
|
|
461
463
|
elif port is not None:
|
|
462
464
|
# Command line port takes precedence
|
|
463
465
|
pass
|
|
464
|
-
|
|
466
|
+
|
|
465
467
|
# Handle parameters from file
|
|
466
468
|
file_parameters = filtered_file_data.get("parameters")
|
|
467
469
|
if file_parameters and parameters is None:
|
|
468
470
|
# Use file parameters if no command line parameters
|
|
469
471
|
parameters = json.dumps(file_parameters) if isinstance(file_parameters, dict) else file_parameters
|
|
470
|
-
|
|
472
|
+
|
|
471
473
|
# Handle secrets from file
|
|
472
474
|
if filtered_file_data.get("secrets"):
|
|
473
475
|
check_data["secrets"] = filtered_file_data["secrets"]
|
|
474
|
-
|
|
476
|
+
|
|
475
477
|
# Handle pw_script_file from file (this is CLI-specific, not sent to SDK)
|
|
476
478
|
if pw_script_file is None and file_data.get("pw_script_file"):
|
|
477
479
|
pw_script_file = file_data["pw_script_file"]
|
|
478
|
-
|
|
480
|
+
|
|
479
481
|
# Show info about ignored fields if any
|
|
480
482
|
ignored_fields = [k for k in file_data.keys() if k not in sdk_fields and k != "pw_script_file"]
|
|
481
483
|
if ignored_fields:
|
|
482
484
|
self.display_info(f"Ignoring non-SDK fields from file: {', '.join(ignored_fields)}")
|
|
483
|
-
|
|
485
|
+
|
|
484
486
|
self.display_info(f"Creating check from file: {from_file}")
|
|
485
487
|
else:
|
|
486
488
|
# Build check data normally
|
|
@@ -703,7 +705,7 @@ class ChecksCommand(BaseCommand):
|
|
|
703
705
|
self.display_error(f"Failed to delete check: {str(e)}")
|
|
704
706
|
raise typer.Exit(1)
|
|
705
707
|
|
|
706
|
-
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):
|
|
707
709
|
"""Get check results using unified results API"""
|
|
708
710
|
try:
|
|
709
711
|
unified_api = self.get_unified_results_client()
|
|
@@ -718,6 +720,10 @@ class ChecksCommand(BaseCommand):
|
|
|
718
720
|
if check_id:
|
|
719
721
|
params["check_id"] = check_id
|
|
720
722
|
|
|
723
|
+
# Add result_id if provided (for fetching specific result)
|
|
724
|
+
if result_id:
|
|
725
|
+
params["result_id"] = result_id
|
|
726
|
+
|
|
721
727
|
# Add optional filters
|
|
722
728
|
if from_date:
|
|
723
729
|
params["start_date"] = from_date
|
|
@@ -740,6 +746,36 @@ class ChecksCommand(BaseCommand):
|
|
|
740
746
|
self.display_info("No results found.")
|
|
741
747
|
return
|
|
742
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
|
+
|
|
743
779
|
# Prepare data for different output formats
|
|
744
780
|
if self.output_format in ['json', 'yaml']:
|
|
745
781
|
results_data = []
|
|
@@ -868,8 +904,7 @@ class ChecksCommand(BaseCommand):
|
|
|
868
904
|
if check_id:
|
|
869
905
|
hints.append(f"Use 'pngr checks result {check_id} <result-id>' for detailed result info")
|
|
870
906
|
else:
|
|
871
|
-
hints.append("Use 'pngr checks result
|
|
872
|
-
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")
|
|
873
908
|
hints.append("Use 'pngr checks list' to see all available checks and their IDs")
|
|
874
909
|
hints.append("Filter results: --type <type>, --status <status>, --region <region>")
|
|
875
910
|
|
|
@@ -936,7 +971,15 @@ class ChecksCommand(BaseCommand):
|
|
|
936
971
|
"""Display detailed result information in a rich format"""
|
|
937
972
|
|
|
938
973
|
# Basic information
|
|
939
|
-
|
|
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
|
+
|
|
940
983
|
basic_info = f"""[bold cyan]Basic Information:[/bold cyan]
|
|
941
984
|
• Result ID: [white]{result.id}[/white]
|
|
942
985
|
• Check ID: [white]{result.check_id}[/white]
|
|
@@ -1036,7 +1079,7 @@ class ChecksCommand(BaseCommand):
|
|
|
1036
1079
|
# Extract region data using actual SDK attributes
|
|
1037
1080
|
region_id = str(region.id) if hasattr(region, 'id') and region.id else "-"
|
|
1038
1081
|
display_name = region.display_name if hasattr(region, 'display_name') and region.display_name else "-"
|
|
1039
|
-
|
|
1082
|
+
|
|
1040
1083
|
# Handle available check types
|
|
1041
1084
|
available_types = "-"
|
|
1042
1085
|
if hasattr(region, 'available_check_types') and region.available_check_types:
|
|
@@ -1052,7 +1095,7 @@ class ChecksCommand(BaseCommand):
|
|
|
1052
1095
|
)
|
|
1053
1096
|
|
|
1054
1097
|
self.console.print(table)
|
|
1055
|
-
|
|
1098
|
+
|
|
1056
1099
|
# Show summary
|
|
1057
1100
|
filter_text = f" for {check_type} checks" if check_type else ""
|
|
1058
1101
|
self.console.print(f"\n[dim]Found {len(response.regions)} regions{filter_text}[/dim]")
|
|
@@ -1115,32 +1158,28 @@ class ChecksCommand(BaseCommand):
|
|
|
1115
1158
|
app = typer.Typer(name="checks", help="🔍 Manage monitoring checks", no_args_is_help=True)
|
|
1116
1159
|
|
|
1117
1160
|
|
|
1118
|
-
def get_output_format():
|
|
1119
|
-
"""Get output format from config"""
|
|
1120
|
-
from ..utils.config import get_config
|
|
1121
|
-
return get_config().get('output_format', 'table')
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
1161
|
@app.command("list")
|
|
1125
1162
|
def list_checks(
|
|
1126
1163
|
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
1127
|
-
page_size: int = typer.Option(20, "--page-size", "-s", help="Items per page
|
|
1164
|
+
page_size: int = typer.Option(20, "--page-size", "-s", help="Items per page"),
|
|
1128
1165
|
check_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by check type (web, api, ssl, tcp, icmp, dns, synthetic, multistep)"),
|
|
1129
1166
|
status: Optional[str] = typer.Option(None, "--status", help="Filter by status. Multiple statuses can be separated by commas (e.g., 'ok,failed')"),
|
|
1130
1167
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by name using case-insensitive partial matching (max 100 chars)"),
|
|
1131
1168
|
group_id: Optional[str] = typer.Option(None, "--group-id", "-g", help="Filter by group ID"),
|
|
1132
1169
|
):
|
|
1133
1170
|
"""List monitoring checks with advanced filtering options"""
|
|
1171
|
+
from ..utils.config import get_output_format
|
|
1172
|
+
|
|
1134
1173
|
# Validate name length
|
|
1135
1174
|
if name and len(name) > 100:
|
|
1136
1175
|
typer.echo("Error: Name filter cannot exceed 100 characters", err=True)
|
|
1137
1176
|
raise typer.Exit(1)
|
|
1138
|
-
|
|
1177
|
+
|
|
1139
1178
|
# Validate check type
|
|
1140
1179
|
if check_type and check_type not in ["web", "api", "ssl", "tcp", "icmp", "dns", "synthetic", "multistep"]:
|
|
1141
1180
|
typer.echo("Error: Invalid check type. Must be one of: web, api, ssl, tcp, icmp, dns, synthetic, multistep", err=True)
|
|
1142
1181
|
raise typer.Exit(1)
|
|
1143
|
-
|
|
1182
|
+
|
|
1144
1183
|
# Validate status values if provided
|
|
1145
1184
|
if status:
|
|
1146
1185
|
valid_statuses = ["ok", "failed", "degraded", "timeout", "pending", "paused"]
|
|
@@ -1149,7 +1188,7 @@ def list_checks(
|
|
|
1149
1188
|
if s not in valid_statuses:
|
|
1150
1189
|
typer.echo(f"Error: Invalid status '{s}'. Must be one of: {', '.join(valid_statuses)}", err=True)
|
|
1151
1190
|
raise typer.Exit(1)
|
|
1152
|
-
|
|
1191
|
+
|
|
1153
1192
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1154
1193
|
checks_cmd.list_checks(page, page_size, check_type, status, name, group_id)
|
|
1155
1194
|
|
|
@@ -1159,6 +1198,7 @@ def get_check(
|
|
|
1159
1198
|
check_id: str = typer.Argument(..., help="Check ID to retrieve"),
|
|
1160
1199
|
):
|
|
1161
1200
|
"""Get specific check details"""
|
|
1201
|
+
from ..utils.config import get_output_format
|
|
1162
1202
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1163
1203
|
checks_cmd.get_check(check_id)
|
|
1164
1204
|
|
|
@@ -1177,11 +1217,11 @@ def create_check(
|
|
|
1177
1217
|
from_file: Optional[str] = typer.Option(None, "--from-file", "-f", help="Path to JSON or YAML file containing check configuration")
|
|
1178
1218
|
):
|
|
1179
1219
|
"""Create a new monitoring check. Can be created from command line options or from a JSON/YAML file.
|
|
1180
|
-
|
|
1220
|
+
|
|
1181
1221
|
When using --from-file:
|
|
1182
1222
|
- Command line options override file values
|
|
1183
1223
|
- File should contain check configuration in JSON or YAML format
|
|
1184
|
-
|
|
1224
|
+
|
|
1185
1225
|
Parameters vary by check type:
|
|
1186
1226
|
- web/api: --url required
|
|
1187
1227
|
- tcp: --host required, --port optional
|
|
@@ -1189,12 +1229,13 @@ def create_check(
|
|
|
1189
1229
|
- icmp: --host required (hostname or IP to ping)
|
|
1190
1230
|
- dns: --host required (domain name to query)
|
|
1191
1231
|
- synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
|
|
1192
|
-
|
|
1232
|
+
from ..utils.config import get_output_format
|
|
1233
|
+
|
|
1193
1234
|
# Validate that we have either from_file or required parameters
|
|
1194
1235
|
if not from_file and not name:
|
|
1195
1236
|
typer.echo("Error: --name is required when not using --from-file", err=True)
|
|
1196
1237
|
raise typer.Exit(1)
|
|
1197
|
-
|
|
1238
|
+
|
|
1198
1239
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1199
1240
|
checks_cmd.create_check(name, check_type, url, host, port, interval, timeout, parameters, pw_script_file, from_file)
|
|
1200
1241
|
|
|
@@ -1213,6 +1254,7 @@ def update_check(
|
|
|
1213
1254
|
pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks")
|
|
1214
1255
|
):
|
|
1215
1256
|
"""Update an existing check. Use --parameters to provide complex parameters like Playwright scripts, regions, etc."""
|
|
1257
|
+
from ..utils.config import get_output_format
|
|
1216
1258
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1217
1259
|
checks_cmd.update_check(check_id, name, url, host, port, interval, timeout, active, parameters, pw_script_file)
|
|
1218
1260
|
|
|
@@ -1223,6 +1265,7 @@ def delete_check(
|
|
|
1223
1265
|
confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
|
|
1224
1266
|
):
|
|
1225
1267
|
"""Delete a monitoring check"""
|
|
1268
|
+
from ..utils.config import get_output_format
|
|
1226
1269
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1227
1270
|
checks_cmd.delete_check(check_id, confirm)
|
|
1228
1271
|
|
|
@@ -1237,10 +1280,12 @@ def get_results(
|
|
|
1237
1280
|
status: Optional[str] = typer.Option(None, "--status", help="Filter by status (ok, failed, degraded, timeout, pending)"),
|
|
1238
1281
|
check_type: Optional[str] = typer.Option(None, "--type", help="Filter by check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
1239
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"),
|
|
1240
1284
|
):
|
|
1241
|
-
"""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
|
|
1242
1287
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1243
|
-
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)
|
|
1244
1289
|
|
|
1245
1290
|
|
|
1246
1291
|
@app.command("result")
|
|
@@ -1250,6 +1295,7 @@ def get_result(
|
|
|
1250
1295
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show full detailed information including network requests"),
|
|
1251
1296
|
):
|
|
1252
1297
|
"""Get detailed information for a specific check result"""
|
|
1298
|
+
from ..utils.config import get_output_format
|
|
1253
1299
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1254
1300
|
checks_cmd.get_check_result_detailed(check_id, result_id, verbose)
|
|
1255
1301
|
|
|
@@ -1259,6 +1305,7 @@ def list_regions(
|
|
|
1259
1305
|
check_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter regions by check type (web, api, tcp, ssl, synthetic, multistep)"),
|
|
1260
1306
|
):
|
|
1261
1307
|
"""List available regions for monitoring checks"""
|
|
1308
|
+
from ..utils.config import get_output_format
|
|
1262
1309
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1263
1310
|
checks_cmd.list_regions(check_type)
|
|
1264
1311
|
|
|
@@ -1269,19 +1316,11 @@ def assign_check_to_group(
|
|
|
1269
1316
|
group_id: Optional[str] = typer.Option(None, "--group-id", "-g", help="Group ID to assign check to (use 'null' to remove from group)"),
|
|
1270
1317
|
):
|
|
1271
1318
|
"""Assign a check to a group or remove it from a group"""
|
|
1319
|
+
from ..utils.config import get_output_format
|
|
1272
1320
|
checks_cmd = ChecksCommand(get_output_format())
|
|
1273
1321
|
checks_cmd.assign_check_to_group(check_id, group_id)
|
|
1274
1322
|
|
|
1275
1323
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
1324
|
# Import on-demand checks functionality from separate module
|
|
1286
1325
|
from .on_demand_checks import run_app, jobs_app
|
|
1287
1326
|
|
|
@@ -1291,8 +1330,12 @@ from .check_groups import app as groups_app
|
|
|
1291
1330
|
# Import check secrets functionality
|
|
1292
1331
|
from .check_secrets import app as check_secrets_app
|
|
1293
1332
|
|
|
1294
|
-
#
|
|
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
|
|
1295
1337
|
app.add_typer(run_app, name="run")
|
|
1296
1338
|
app.add_typer(jobs_app, name="jobs")
|
|
1297
1339
|
app.add_typer(groups_app, name="groups")
|
|
1298
|
-
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")
|