From 3cf37a3389c28aeb4b035876a421e5be5034f155 Mon Sep 17 00:00:00 2001 From: "vy.boyko" Date: Sat, 25 Oct 2025 21:11:59 +0300 Subject: [PATCH] namespaces favouites --- README.md | 17 +++++- k8s_tool/config.py | 87 ++++++++++++++++++++++++++++ k8s_tool/main.py | 141 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 k8s_tool/config.py diff --git a/README.md b/README.md index 43672c3..3e3055d 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,13 @@ ## Особенности - 🎯 Интерактивное меню с навигацией стрелками +- ⭐ Избранные namespaces для быстрого доступа - 🎨 Красивый вывод с использованием Rich - 📦 Управление namespaces, deployments, pods, ConfigMaps - 🔄 Быстрый рестарт и масштабирование deployments - 📝 Просмотр логов pods - 🔍 Просмотр содержимого ConfigMaps +- 💾 Сохранение настроек в `~/.config/k8s-tool/k8s-tool.cfg` ## Требования @@ -58,6 +60,18 @@ poetry run k8s-tool #### Выбор Namespace Выберите namespace для работы. Все последующие операции будут выполняться в выбранном namespace. +Избранные namespaces отображаются в начале списка с пометкой ⭐ для быстрого доступа. Список избранных сохраняется между запусками приложения. + +#### Управление избранными +Добавляйте часто используемые namespaces в избранное для быстрого доступа: +- Добавить/удалить текущий namespace в/из избранного +- Просмотр всех избранных namespaces +- Добавление любого namespace в избранное из полного списка +- Удаление namespace из избранного +- Очистка всех избранных + +Конфигурация сохраняется в файле `~/.config/k8s-tool/k8s-tool.cfg`. + #### Список Deployments Отображает все deployments в текущем namespace с информацией о: - Имени deployment @@ -92,9 +106,10 @@ poetry run k8s-tool ## Архитектура -Проект состоит из двух основных модулей: +Проект состоит из трёх основных модулей: - `k8s_client.py` - обёртка над Kubernetes Python API для работы с кластером +- `config.py` - менеджер конфигурации для управления избранными namespaces - `main.py` - главное приложение с интерактивным меню ### Технологии diff --git a/k8s_tool/config.py b/k8s_tool/config.py new file mode 100644 index 0000000..2a85f04 --- /dev/null +++ b/k8s_tool/config.py @@ -0,0 +1,87 @@ +"""Configuration manager for k8s-tool.""" + +import json +import os +from pathlib import Path +from typing import List, Dict, Any +from rich.console import Console + +console = Console() + + +class ConfigManager: + """Manage application configuration.""" + + def __init__(self): + """Initialize configuration manager.""" + self.config_dir = Path.home() / ".config" / "k8s-tool" + self.config_file = self.config_dir / "k8s-tool.cfg" + self._ensure_config_dir() + self.config = self._load_config() + + def _ensure_config_dir(self): + """Ensure configuration directory exists.""" + try: + self.config_dir.mkdir(parents=True, exist_ok=True) + except Exception as e: + console.print(f"[yellow]Warning:[/yellow] Could not create config directory: {e}") + + def _load_config(self) -> Dict[str, Any]: + """Load configuration from file.""" + if not self.config_file.exists(): + return {"favorites": []} + + try: + with open(self.config_file, 'r') as f: + return json.load(f) + except Exception as e: + console.print(f"[yellow]Warning:[/yellow] Could not load config: {e}") + return {"favorites": []} + + def _save_config(self): + """Save configuration to file.""" + try: + with open(self.config_file, 'w') as f: + json.dump(self.config, f, indent=2) + except Exception as e: + console.print(f"[red]Error:[/red] Could not save config: {e}") + + def get_favorites(self) -> List[str]: + """Get list of favorite namespaces.""" + return self.config.get("favorites", []) + + def add_favorite(self, namespace: str) -> bool: + """Add namespace to favorites.""" + favorites = self.config.get("favorites", []) + if namespace not in favorites: + favorites.append(namespace) + self.config["favorites"] = sorted(favorites) + self._save_config() + console.print(f"[green]✓[/green] Added [cyan]{namespace}[/cyan] to favorites") + return True + else: + console.print(f"[yellow]![/yellow] [cyan]{namespace}[/cyan] is already in favorites") + return False + + def remove_favorite(self, namespace: str) -> bool: + """Remove namespace from favorites.""" + favorites = self.config.get("favorites", []) + if namespace in favorites: + favorites.remove(namespace) + self.config["favorites"] = favorites + self._save_config() + console.print(f"[green]✓[/green] Removed [cyan]{namespace}[/cyan] from favorites") + return True + else: + console.print(f"[yellow]![/yellow] [cyan]{namespace}[/cyan] is not in favorites") + return False + + def is_favorite(self, namespace: str) -> bool: + """Check if namespace is in favorites.""" + return namespace in self.config.get("favorites", []) + + def clear_favorites(self): + """Clear all favorites.""" + self.config["favorites"] = [] + self._save_config() + console.print("[green]✓[/green] Cleared all favorites") diff --git a/k8s_tool/main.py b/k8s_tool/main.py index 463dc9f..79e96db 100644 --- a/k8s_tool/main.py +++ b/k8s_tool/main.py @@ -9,6 +9,7 @@ from rich.panel import Panel from rich.syntax import Syntax from k8s_tool.k8s_client import K8sClient +from k8s_tool.config import ConfigManager console = Console() @@ -32,6 +33,7 @@ class K8sTool: def __init__(self): """Initialize the application.""" self.k8s_client = K8sClient() + self.config = ConfigManager() self.current_namespace: Optional[str] = None def run(self): @@ -58,10 +60,13 @@ class K8sTool: def _main_menu(self): """Display main menu.""" namespace_info = f"[cyan]{self.current_namespace}[/cyan]" if self.current_namespace else "[dim]not selected[/dim]" - console.print(f"\n[bold]Current namespace:[/bold] {namespace_info}") + is_favorite = self.config.is_favorite(self.current_namespace) if self.current_namespace else False + fav_indicator = " ⭐" if is_favorite else "" + console.print(f"\n[bold]Current namespace:[/bold] {namespace_info}{fav_indicator}") choices = [ "Select Namespace", + "Manage Favorites", "List Deployments", "Restart Deployment", "Scale Deployment", @@ -78,6 +83,8 @@ class K8sTool: if action == "Select Namespace": self._select_namespace() + elif action == "Manage Favorites": + self._manage_favorites() elif action == "List Deployments": self._list_deployments() elif action == "Restart Deployment": @@ -101,15 +108,139 @@ class K8sTool: console.print("[red]No namespaces found[/red]") return - namespace = questionary.select( + # Get favorites + favorites = self.config.get_favorites() + + # Separate favorites and non-favorites + fav_namespaces = [ns for ns in namespaces if ns in favorites] + other_namespaces = [ns for ns in namespaces if ns not in favorites] + + # Create choices with stars for favorites + choices = [] + if fav_namespaces: + choices.extend([f"⭐ {ns}" for ns in fav_namespaces]) + if other_namespaces: + choices.append(questionary.Separator("─" * 40)) + choices.extend(other_namespaces) + + selected = questionary.select( "Select namespace:", - choices=namespaces, + choices=choices, + style=custom_style + ).ask() + + if selected: + # Remove star prefix if present + namespace = selected.replace("⭐ ", "") + self.current_namespace = namespace + is_fav = self.config.is_favorite(namespace) + fav_text = " (favorite)" if is_fav else "" + console.print(f"[green]✓[/green] Namespace set to: [cyan]{namespace}[/cyan]{fav_text}") + + def _manage_favorites(self): + """Manage favorite namespaces.""" + choices = [] + + if self.current_namespace: + if self.config.is_favorite(self.current_namespace): + choices.append(f"Remove '{self.current_namespace}' from favorites") + else: + choices.append(f"Add '{self.current_namespace}' to favorites") + + choices.extend([ + "View all favorites", + "Add namespace to favorites", + "Remove namespace from favorites", + "Clear all favorites", + "Back to main menu" + ]) + + action = questionary.select( + "Favorites management:", + choices=choices, + style=custom_style + ).ask() + + if not action or action == "Back to main menu": + return + + if action.startswith("Add '") and action.endswith("' to favorites"): + # Add current namespace + self.config.add_favorite(self.current_namespace) + + elif action.startswith("Remove '") and action.endswith("' from favorites"): + # Remove current namespace + self.config.remove_favorite(self.current_namespace) + + elif action == "View all favorites": + self._view_favorites() + + elif action == "Add namespace to favorites": + self._add_namespace_to_favorites() + + elif action == "Remove namespace from favorites": + self._remove_namespace_from_favorites() + + elif action == "Clear all favorites": + if questionary.confirm("Are you sure you want to clear all favorites?", style=custom_style, default=False).ask(): + self.config.clear_favorites() + + def _view_favorites(self): + """View all favorite namespaces.""" + favorites = self.config.get_favorites() + + if not favorites: + console.print("[yellow]No favorites yet[/yellow]") + return + + console.print("\n[bold]Favorite namespaces:[/bold]") + for ns in favorites: + indicator = " [dim](current)[/dim]" if ns == self.current_namespace else "" + console.print(f" ⭐ [cyan]{ns}[/cyan]{indicator}") + console.print() + + def _add_namespace_to_favorites(self): + """Add a namespace to favorites.""" + console.print("[dim]Fetching namespaces...[/dim]") + namespaces = self.k8s_client.get_namespaces() + + if not namespaces: + console.print("[red]No namespaces found[/red]") + return + + # Filter out already favorite namespaces + favorites = self.config.get_favorites() + available = [ns for ns in namespaces if ns not in favorites] + + if not available: + console.print("[yellow]All namespaces are already in favorites[/yellow]") + return + + namespace = questionary.select( + "Select namespace to add to favorites:", + choices=available, style=custom_style ).ask() if namespace: - self.current_namespace = namespace - console.print(f"[green]✓[/green] Namespace set to: [cyan]{namespace}[/cyan]") + self.config.add_favorite(namespace) + + def _remove_namespace_from_favorites(self): + """Remove a namespace from favorites.""" + favorites = self.config.get_favorites() + + if not favorites: + console.print("[yellow]No favorites to remove[/yellow]") + return + + namespace = questionary.select( + "Select namespace to remove from favorites:", + choices=favorites, + style=custom_style + ).ask() + + if namespace: + self.config.remove_favorite(namespace) def _ensure_namespace_selected(self) -> bool: """Ensure namespace is selected."""