From 6461b7e906a99fbd71174c8628ce16ef8ab2769f Mon Sep 17 00:00:00 2001 From: "dingfeng.wong" Date: Thu, 10 Jul 2025 00:21:43 +0800 Subject: [PATCH] grafana --- src/elf/cli.py | 4 ++ src/elf/grafana_cli.py | 132 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 src/elf/grafana_cli.py diff --git a/src/elf/cli.py b/src/elf/cli.py index a30c6ed..e3bbcf0 100644 --- a/src/elf/cli.py +++ b/src/elf/cli.py @@ -1,6 +1,7 @@ import typer from rich.console import Console from .wrap_cli import wrap_app +from .grafana_cli import grafana_app app = typer.Typer() console = Console() @@ -8,5 +9,8 @@ console = Console() # Add the wrap subcommand to the main app app.add_typer(wrap_app, name="wrap") +# Add the grafana subcommand to the main app +app.add_typer(grafana_app, name="grafana") + if __name__ == "__main__": app() \ No newline at end of file diff --git a/src/elf/grafana_cli.py b/src/elf/grafana_cli.py new file mode 100644 index 0000000..0679c88 --- /dev/null +++ b/src/elf/grafana_cli.py @@ -0,0 +1,132 @@ +import typer +from rich.console import Console +import json +import pyperclip +from typing import Dict, Any, List, Optional +from pathlib import Path + +console = Console() + +# Create the main grafana subcommand +grafana_app = typer.Typer() + +def safe_get(data: Dict[str, Any], key: str, default: Any = None) -> Any: + """Safely get a value from a dictionary.""" + return data.get(key, default) + +def mutate_panel(panel: Dict[str, Any]) -> None: + """Mutate a single panel by setting repeat and repeatDirection fields.""" + # Check if type is "row" and remove repeat key if it exists + if safe_get(panel, 'type') == 'row': + if 'repeat' in panel: + del panel['repeat'] + else: + # For non-row panels, set repeat and repeatDirection + panel['repeat'] = 'instance' + panel['repeatDirection'] = 'h' + panel['maxPerRow'] = 12 + +def mutate_panels_recursive(panels: List[Dict[str, Any]]) -> None: + """Recursively mutate panels, handling nested panels.""" + for panel in panels: + if not isinstance(panel, dict): + continue + + # Mutate current panel + mutate_panel(panel) + + # Check for nested panels + nested_panels = safe_get(panel, 'panels', []) + if nested_panels and isinstance(nested_panels, list): + mutate_panels_recursive(nested_panels) + +def mutate_grafana_json(data: Dict[str, Any]) -> Dict[str, Any]: + """ + Mutate Grafana JSON by setting repeat and repeatDirection fields. + + Args: + data: The Grafana dashboard JSON data + + Returns: + The mutated JSON data + """ + # Make a copy to avoid mutating the original + mutated_data = json.loads(json.dumps(data)) + + # Get panels safely + panels = safe_get(mutated_data, 'panels', []) + + if panels and isinstance(panels, list): + mutate_panels_recursive(panels) + + return mutated_data + +@grafana_app.command("mutate", short_help="Mutate Grafana JSON to set repeat fields.") +def mutate_dashboard( + input_file: Optional[Path] = typer.Option( + None, "--input-file", "-i", help="Input JSON file path. If not provided, reads from clipboard." + ), + output_file: Optional[Path] = typer.Option( + None, "--output-file", "-o", help="Output JSON file path. If not provided, outputs to clipboard." + ) +): + """ + Mutate Grafana JSON by setting repeat="instance" and repeatDirection="h". + + This command processes Grafana dashboard JSON and modifies panels to: + - Set repeat field to "instance" + - Set repeatDirection field to "h" + - Set maxPerRow to 12 + - Remove repeat field from row-type panels + """ + console.print("[blue]Processing Grafana dashboard JSON...[/blue]") + + # Read input + try: + if input_file: + console.print(f"[blue]Reading from file: {input_file}[/blue]") + if not input_file.exists(): + console.print(f"[red]Error: Input file '{input_file}' not found[/red]") + raise typer.Exit(code=1) + + json_content = input_file.read_text(encoding='utf-8') + else: + console.print("[blue]Reading from clipboard...[/blue]") + json_content = pyperclip.paste() + if not json_content.strip(): + console.print("[red]Error: Clipboard is empty or contains no text.[/red]") + raise typer.Exit(code=1) + except Exception as e: + console.print(f"[red]Error reading input: {e}[/red]") + raise typer.Exit(code=1) + + # Parse JSON + try: + data = json.loads(json_content) + except json.JSONDecodeError as e: + console.print(f"[red]Error parsing JSON: {e}[/red]") + raise typer.Exit(code=1) + + # Mutate the data + try: + mutated_data = mutate_grafana_json(data) + console.print("[green]Successfully mutated Grafana JSON![/green]") + except Exception as e: + console.print(f"[red]Error mutating JSON: {e}[/red]") + raise typer.Exit(code=1) + + # Output result + try: + output_json = json.dumps(mutated_data, indent=2, ensure_ascii=False) + + if output_file: + console.print(f"[blue]Writing to file: {output_file}[/blue]") + output_file.write_text(output_json, encoding='utf-8') + console.print(f"[green]Output written to {output_file}[/green]") + else: + console.print("[blue]Copying to clipboard...[/blue]") + pyperclip.copy(output_json) + console.print("[green]Output copied to clipboard![/green]") + except Exception as e: + console.print(f"[red]Error writing output: {e}[/red]") + raise typer.Exit(code=1) \ No newline at end of file