pingera-cli 0.1.13__tar.gz → 0.1.14__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.
Files changed (55) hide show
  1. {pingera_cli-0.1.13/pingera_cli.egg-info → pingera_cli-0.1.14}/PKG-INFO +1 -1
  2. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/__init__.py +1 -1
  3. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/on_demand_checks.py +120 -23
  4. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/main.py +3 -2
  5. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/utils/config.py +26 -0
  6. {pingera_cli-0.1.13 → pingera_cli-0.1.14/pingera_cli.egg-info}/PKG-INFO +1 -1
  7. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli.egg-info/SOURCES.txt +1 -0
  8. pingera_cli-0.1.14/pwscript.js +14 -0
  9. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/.gitignore +0 -0
  10. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/.replit +0 -0
  11. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/LICENSE +0 -0
  12. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/README.md +0 -0
  13. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/assert +0 -0
  14. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/docs/cli-commands.md +0 -0
  15. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/docs/example-check.yaml +0 -0
  16. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/docs/example-synthetic-screenshot.json +0 -0
  17. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/docs/example-web-check.json +0 -0
  18. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/__main__.py +0 -0
  19. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/__init__.py +0 -0
  20. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/auth.py +0 -0
  21. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/base.py +0 -0
  22. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/check_groups.py +0 -0
  23. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/check_secrets.py +0 -0
  24. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/checks.py +0 -0
  25. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/execution_groups.py +0 -0
  26. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/commands/secrets.py +0 -0
  27. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/__init__.py +0 -0
  28. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/base_formatter.py +0 -0
  29. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/dns_formatter.py +0 -0
  30. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/generic_formatter.py +0 -0
  31. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/icmp_formatter.py +0 -0
  32. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/multistep_formatter.py +0 -0
  33. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/registry.py +0 -0
  34. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/ssl_formatter.py +0 -0
  35. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/synthetic_formatter.py +0 -0
  36. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/formatters/web_formatter.py +0 -0
  37. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/utils/__init__.py +0 -0
  38. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/utils/console.py +0 -0
  39. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli/utils/file_utils.py +0 -0
  40. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli.egg-info/dependency_links.txt +0 -0
  41. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli.egg-info/entry_points.txt +0 -0
  42. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli.egg-info/requires.txt +0 -0
  43. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pingera_cli.egg-info/top_level.txt +0 -0
  44. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/pyproject.toml +0 -0
  45. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/replit.md +0 -0
  46. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/run_tests.py +0 -0
  47. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/setup.cfg +0 -0
  48. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/setup.py +0 -0
  49. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/tests/__init__.py +0 -0
  50. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/tests/conftest.py +0 -0
  51. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/tests/requirements.txt +0 -0
  52. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/tests/test_auth.py +0 -0
  53. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/tests/test_checks.py +0 -0
  54. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/tests/test_config.py +0 -0
  55. {pingera_cli-0.1.13 → pingera_cli-0.1.14}/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.13
3
+ Version: 0.1.14
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
@@ -2,7 +2,7 @@
2
2
  Pingera CLI - A beautiful CLI tool for Pingera Platform
3
3
  """
4
4
 
5
- __version__ = "0.1.13"
5
+ __version__ = "0.1.14"
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
 
@@ -19,8 +19,9 @@ class OnDemandChecksCommand(BaseCommand):
19
19
  Commands for managing on-demand monitoring checks
20
20
  """
21
21
 
22
- def __init__(self, output_format: Optional[str] = None):
22
+ def __init__(self, output_format: Optional[str] = None, verbose: bool = False):
23
23
  super().__init__(output_format)
24
+ self.verbose = verbose
24
25
 
25
26
  def _parse_check_file(self, file_path: str) -> dict:
26
27
  """Parse check configuration from JSON or YAML file (local or URL)"""
@@ -446,15 +447,17 @@ class OnDemandChecksCommand(BaseCommand):
446
447
  }
447
448
  self.output_data(job_data)
448
449
  else:
449
- # Rich formatted detailed view
450
- self._display_detailed_job_status(job_status, job_id)
450
+ # Rich formatted detailed view - fetch and display with results
451
+ self._fetch_and_display_job_result(job_id, job_status, verbose=self.verbose)
451
452
 
452
453
  except Exception as e:
453
454
  self.display_error(f"Failed to get job status: {str(e)}")
454
455
  raise typer.Exit(1)
455
456
 
456
- def _fetch_and_display_job_result(self, job_id: str, job_status):
457
+ def _fetch_and_display_job_result(self, job_id: str, job_status, verbose: bool = False):
457
458
  """Fetch detailed results from the new results endpoint and display them"""
459
+ import time
460
+
458
461
  try:
459
462
  # Extract result_id(s) from job response
460
463
  result_ids = []
@@ -463,14 +466,56 @@ class OnDemandChecksCommand(BaseCommand):
463
466
  if hasattr(job_status, 'result') and job_status.result:
464
467
  result = job_status.result
465
468
 
469
+ # Debug: Print the raw result to see what we're getting (only in verbose mode)
470
+ if verbose and self.output_format not in ['json', 'yaml']:
471
+ import json
472
+ self.console.print(f"[dim]DEBUG: Raw result from job status:[/dim]")
473
+ self.console.print(f"[dim]{json.dumps(result, indent=2, default=str)}[/dim]")
474
+
466
475
  # Check if this is a multi-region result
467
476
  if isinstance(result, dict):
468
477
  if 'regional_summary' in result and result['regional_summary']:
469
478
  # Multi-region execution
470
479
  is_multi_region = True
471
- for regional_result in result['regional_summary']:
480
+ regional_summary = result['regional_summary']
481
+
482
+ # Debug: Show what we found (only in verbose mode)
483
+ if verbose and self.output_format not in ['json', 'yaml']:
484
+ self.console.print(f"[dim]DEBUG: Found {len(regional_summary)} regions in regional_summary[/dim]")
485
+
486
+ for regional_result in regional_summary:
472
487
  if 'result_id' in regional_result:
473
488
  result_ids.append(regional_result['result_id'])
489
+
490
+ # Check if we might be missing results (race condition detection)
491
+ total_regions = result.get('total_regions', len(regional_summary))
492
+ completed_regions = result.get('completed_regions', len(regional_summary))
493
+
494
+ if verbose and self.output_format not in ['json', 'yaml']:
495
+ self.console.print(f"[dim]DEBUG: total_regions={total_regions}, completed_regions={completed_regions}, found result_ids={len(result_ids)}[/dim]")
496
+
497
+ # If we're missing results, wait a bit and refetch
498
+ if len(result_ids) < total_regions:
499
+ if self.output_format not in ['json', 'yaml']:
500
+ self.console.print(f"[yellow]⚠ Race condition detected: {len(result_ids)}/{total_regions} results ready. Waiting 2s and retrying...[/yellow]")
501
+ time.sleep(2)
502
+
503
+ # Refetch job status
504
+ checks_api = self.get_client()
505
+ job_status = checks_api.v1_checks_jobs_job_id_get(job_id=job_id)
506
+
507
+ # Re-extract result_ids
508
+ result_ids = []
509
+ if hasattr(job_status, 'result') and job_status.result:
510
+ result = job_status.result
511
+ if isinstance(result, dict) and 'regional_summary' in result:
512
+ for regional_result in result['regional_summary']:
513
+ if 'result_id' in regional_result:
514
+ result_ids.append(regional_result['result_id'])
515
+
516
+ if verbose and self.output_format not in ['json', 'yaml']:
517
+ self.console.print(f"[dim]DEBUG: After retry, found {len(result_ids)} result_ids[/dim]")
518
+
474
519
  elif 'result_id' in result:
475
520
  # Single-region execution
476
521
  result_ids.append(result['result_id'])
@@ -487,14 +532,14 @@ class OnDemandChecksCommand(BaseCommand):
487
532
  raise typer.Exit(1)
488
533
 
489
534
  from pingera import ApiClient, Configuration
490
- from pingera.api import ChecksApi
535
+ from pingera.api import ChecksUnifiedResultsApi
491
536
  from ..utils.config import get_config
492
537
 
493
538
  configuration = Configuration()
494
539
  configuration.host = get_config().get('base_url', 'https://api.pingera.ru')
495
540
  configuration.api_key['apiKeyAuth'] = api_key
496
541
  api_client = ApiClient(configuration)
497
- checks_api = ChecksApi(api_client)
542
+ checks_api = ChecksUnifiedResultsApi(api_client)
498
543
 
499
544
  # Fetch detailed results for each result_id
500
545
  if is_multi_region:
@@ -547,13 +592,15 @@ class OnDemandChecksCommand(BaseCommand):
547
592
  self.console.print(f"\n[bold cyan]Multi-Region Execution Result[/bold cyan]")
548
593
  self.console.print(f"• Job ID: [white]{job_id}[/white]")
549
594
  self.console.print(f"• Status: [{status_color}]{status_emoji} {job_status.status}[/{status_color}]")
550
- self.console.print(f"• Regions: [white]{len(result_ids)}[/white]")
551
595
 
552
596
  # Extract and display aggregated statistics
553
597
  if hasattr(job_status, 'result') and isinstance(job_status.result, dict):
554
598
  result = job_status.result
555
599
  regional_summary = result.get('regional_summary', [])
556
600
 
601
+ # Display region count from actual regional_summary
602
+ self.console.print(f"• Regions: [white]{len(regional_summary)}[/white]")
603
+
557
604
  if regional_summary:
558
605
  # Calculate aggregated statistics
559
606
  response_times = []
@@ -616,19 +663,63 @@ class OnDemandChecksCommand(BaseCommand):
616
663
 
617
664
  def _display_detailed_result_with_job_info(self, job_status, job_id: str, detailed_result):
618
665
  """Display detailed result information combined with job info"""
619
- # Use the existing detailed result display logic from checks.py
620
- # For now, use the job status display and note about detailed results
621
- self._display_detailed_job_status(job_status, job_id)
666
+ from rich.panel import Panel
667
+ from ..formatters.registry import FormatterRegistry
668
+
669
+ # Determine status color and emoji
670
+ status_emoji = "✅" if hasattr(detailed_result, 'status') and detailed_result.status == 'ok' else "❌"
671
+ status_color = "green" if hasattr(detailed_result, 'status') and detailed_result.status == 'ok' else "red"
672
+
673
+ # Basic information
674
+ basic_info = f"""[bold cyan]Basic Information:[/bold cyan]
675
+ • Result ID: [white]{detailed_result.id if hasattr(detailed_result, 'id') else 'Unknown'}[/white]
676
+ • Check ID: [white]{detailed_result.check_id if hasattr(detailed_result, 'check_id') and detailed_result.check_id else 'None'}[/white]
677
+ • Status: [{status_color}]{status_emoji} {detailed_result.status if hasattr(detailed_result, 'status') else 'Unknown'}[/{status_color}]
678
+ • Timestamp: [white]{detailed_result.created_at.strftime('%Y-%m-%d %H:%M:%S UTC') if hasattr(detailed_result, 'created_at') and detailed_result.created_at else 'Unknown'}[/white]"""
679
+
680
+ if hasattr(detailed_result, 'response_time') and detailed_result.response_time:
681
+ basic_info += f"\n• Response Time: [yellow]{detailed_result.response_time}ms[/yellow]"
622
682
 
623
- # Additional result details if available
683
+ # Server information
684
+ if hasattr(detailed_result, 'check_server') and detailed_result.check_server:
685
+ server = detailed_result.check_server
686
+ server_info = f"""• Check Server: [white]{server.id if hasattr(server, 'id') else 'Unknown'}[/white]
687
+ • Server IP: [white]{server.ip_address if hasattr(server, 'ip_address') else 'Unknown'}[/white]
688
+ • Server Country: [white]{server.country if hasattr(server, 'country') else 'Unknown'}[/white]
689
+ • Server Region: [white]{server.region if hasattr(server, 'region') else 'Unknown'}[/white]"""
690
+ basic_info += f"\n{server_info}"
691
+
692
+ # Error information
693
+ error_info = ""
694
+ if hasattr(detailed_result, 'error_message') and detailed_result.error_message:
695
+ error_info = f"""
696
+ [bold red]Error Information:[/bold red]
697
+ • Error: [red]{detailed_result.error_message}[/red]"""
698
+
699
+ # Metadata analysis using formatters
700
+ metadata_info = ""
624
701
  if hasattr(detailed_result, 'check_metadata') and detailed_result.check_metadata:
625
- from ..formatters.registry import FormatterRegistry
626
- registry = FormatterRegistry(verbose=False)
627
- metadata_formatted = registry.format_metadata(detailed_result.check_metadata)
702
+ metadata = detailed_result.check_metadata
628
703
 
629
- if metadata_formatted.strip():
630
- self.console.print(f"\n[bold cyan]Detailed Result Metadata:[/bold cyan]")
631
- self.console.print(metadata_formatted)
704
+ if isinstance(metadata, dict):
705
+ registry = FormatterRegistry(verbose=self.verbose)
706
+ metadata_info = registry.format_metadata(metadata)
707
+
708
+ # Combine all sections
709
+ full_info = basic_info
710
+ if error_info:
711
+ full_info += error_info
712
+ if metadata_info:
713
+ full_info += metadata_info
714
+
715
+ panel = Panel(
716
+ full_info,
717
+ title=f"🔍 Result Details: {detailed_result.id if hasattr(detailed_result, 'id') else 'Unknown'}",
718
+ border_style="blue",
719
+ padding=(1, 2),
720
+ )
721
+
722
+ self.console.print(panel)
632
723
 
633
724
  def _display_detailed_job_status(self, job_status, job_id: str):
634
725
  """Display detailed job status information in a rich format"""
@@ -806,7 +897,7 @@ class OnDemandChecksCommand(BaseCommand):
806
897
  time.sleep(0.5) # Brief pause to show completion
807
898
 
808
899
  # Fetch and display the actual result
809
- self._fetch_and_display_job_result(job_id, job_status)
900
+ self._fetch_and_display_job_result(job_id, job_status, verbose=self.verbose)
810
901
  return
811
902
 
812
903
  elif job_status.status == 'running':
@@ -858,6 +949,12 @@ def get_output_format():
858
949
  return get_config().get('output_format', 'table')
859
950
 
860
951
 
952
+ def get_verbose_mode():
953
+ """Get verbose mode from config"""
954
+ from ..utils.config import get_verbose_mode as _get_verbose_mode
955
+ return _get_verbose_mode()
956
+
957
+
861
958
  @run_app.command("custom")
862
959
  def run_custom_check(
863
960
  url: Optional[str] = typer.Option(None, "--url", "-u", help="URL to monitor (required for web, api, ssl checks)"),
@@ -885,7 +982,7 @@ def run_custom_check(
885
982
  - tcp: --host required, --port optional
886
983
  - ssl: --url or --host required
887
984
  - synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
888
- on_demand_cmd = OnDemandChecksCommand(get_output_format())
985
+ on_demand_cmd = OnDemandChecksCommand(get_output_format(), verbose=get_verbose_mode())
889
986
  on_demand_cmd.execute_custom_check(url, check_type, host, port, timeout, name, parameters, pw_script_file, from_file, not no_wait)
890
987
 
891
988
 
@@ -895,7 +992,7 @@ def run_existing_check(
895
992
  no_wait: bool = typer.Option(False, "--no-wait", help="Don't wait for job completion, just queue the check and return job ID"),
896
993
  ):
897
994
  """Execute existing check on demand. By default, waits for job completion and shows result immediately (max 5 minutes). Use --no-wait to just queue the check."""
898
- on_demand_cmd = OnDemandChecksCommand(get_output_format())
995
+ on_demand_cmd = OnDemandChecksCommand(get_output_format(), verbose=get_verbose_mode())
899
996
  on_demand_cmd.execute_existing_check(check_id, not no_wait)
900
997
 
901
998
 
@@ -905,7 +1002,7 @@ def list_jobs(
905
1002
  page_size: int = typer.Option(20, "--page-size", "-s", help="Items per page"),
906
1003
  ):
907
1004
  """List check jobs"""
908
- on_demand_cmd = OnDemandChecksCommand(get_output_format())
1005
+ on_demand_cmd = OnDemandChecksCommand(get_output_format(), verbose=get_verbose_mode())
909
1006
  on_demand_cmd.list_jobs(page, page_size)
910
1007
 
911
1008
 
@@ -914,5 +1011,5 @@ def get_job_status(
914
1011
  job_id: str = typer.Argument(..., help="Job ID to get status for"),
915
1012
  ):
916
1013
  """Get job status and results. For multi-region executions, shows aggregated statistics and regional summary."""
917
- on_demand_cmd = OnDemandChecksCommand(get_output_format())
1014
+ on_demand_cmd = OnDemandChecksCommand(get_output_format(), verbose=get_verbose_mode())
918
1015
  on_demand_cmd.get_job_status(job_id)
@@ -196,9 +196,10 @@ def main(
196
196
  if verbose:
197
197
  console.print("[dim]Verbose mode enabled[/dim]")
198
198
 
199
- # Store current output format in config temporarily for subcommands
200
- from .utils.config import set_output_format as save_output_format
199
+ # Store current output format and verbose mode in config temporarily for subcommands
200
+ from .utils.config import set_output_format as save_output_format, set_verbose_mode
201
201
  save_output_format(output)
202
+ set_verbose_mode(verbose)
202
203
 
203
204
 
204
205
  def cli_entry_point():
@@ -158,6 +158,32 @@ def get_output_format() -> str:
158
158
  return config.get('output_format', 'table')
159
159
 
160
160
 
161
+ def set_verbose_mode(verbose: bool) -> bool:
162
+ """
163
+ Set verbose mode in configuration file
164
+
165
+ Args:
166
+ verbose: Verbose mode flag
167
+
168
+ Returns:
169
+ bool: True if saved successfully, False otherwise
170
+ """
171
+ config = get_config()
172
+ config['verbose'] = verbose
173
+ return save_config(config)
174
+
175
+
176
+ def get_verbose_mode() -> bool:
177
+ """
178
+ Get verbose mode from configuration
179
+
180
+ Returns:
181
+ bool: Verbose mode flag (default: False)
182
+ """
183
+ config = get_config()
184
+ return config.get('verbose', False)
185
+
186
+
161
187
  def validate_config() -> Dict[str, bool]:
162
188
  """
163
189
  Validate current configuration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingera-cli
3
- Version: 0.1.13
3
+ Version: 0.1.14
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
@@ -3,6 +3,7 @@
3
3
  LICENSE
4
4
  README.md
5
5
  assert
6
+ pwscript.js
6
7
  pyproject.toml
7
8
  replit.md
8
9
  run_tests.py
@@ -0,0 +1,14 @@
1
+ const { test, expect } = require('@playwright/test');
2
+
3
+ test('Basic Screenshot', async ({ page }) => {
4
+ // Navigate to the page
5
+ await page.goto('https://playwright.dev/');
6
+
7
+ // Wait for the page to be in a stable state (e.g., network idle)
8
+ await page.waitForLoadState('load');
9
+
10
+ // Take a full-page screenshot
11
+ await page.screenshot({ path: 'screenshot.png', fullPage: true });
12
+
13
+ console.log('Successfully took a full-page screenshot.');
14
+ });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes