feat(backend): add database versioning
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
█████████████████████████████████████████████▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▓▓████████████████████████████
|
||||
███████████████████████████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░▒▒▒████████████████████
|
||||
████████████████████████████▓▒▒▒▒▒▒▒▒▓▓████████████████████████████████▓▒▒▒▒▒▒░░░░░▒▓███████████████
|
||||
██████████████████████▓▒▒▒▒▓██████████████████████████████████████████████████▒▒▒▒░░░░░▒████████████
|
||||
█████████████████▓▒▒▓████████████████████████████████████████████████████████████▓▒▒▒░░░░▒██████████
|
||||
█████████████▓▓▓████████████████████████████████████████████████████████████████████▒▒▒░░░░▒████████
|
||||
██████████▓██████████████████████████████████████████████████████████████████████████▓▒▒░░░░▒███████
|
||||
███████████████████████████████████████▓▒▒▒▒▒▒▒▒▒▒▒███████████████████████████████████▓▒░░░░░▒██████
|
||||
████████████████████████▓▓▓▓█████████▓▒▒▒▒▒░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒█████████████████████████▒▒░░░░▒▓█████
|
||||
███████████████████▓▒▒▒▒░░░░░░▒▓▓▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░▒▒██████████████████████▓▒░░░░░▒▒█████
|
||||
█████████████████▓▒▒▒░░░░░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒█████████████████████▒░░░░░▒▒▓█████
|
||||
████████████████▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒███████████████████▒░░░░░▒▒▒▓█████
|
||||
███████████████▓▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒████████████████▓▒░░░░░░▒▒▒▒██████
|
||||
███████████████▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒██████████████▒░░░░░░░▒▒▒▒▒███████
|
||||
█████████████▓▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░▒▒▒▒▒▓███████▓▒░░░░░░░░░▒▒▒▒▒▓████████
|
||||
███████████▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░▒▒▒▒▓▓▒░░░░░░░░░░░░▒▒▒▒▒▒██████████
|
||||
██████████▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░░░░░░░░░░░░▒▒▒▒░░░░░░░░░░▒▒▒▒▒▒▒▒▓███████████
|
||||
█████████▒▒▒▒░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░░░░░░▒▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒▓██████████████
|
||||
███████▒▒▒▒░░░░░░░░░░▒▒▒▒▒░░░░░░░░▒▒▒░░░░░░░░░░░░░░▒▒▒▒░░░░░▒░░░░▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▓████████████████
|
||||
██████▒▒▒▒░░░░░░░░░░▒▒▒░░░░░░░░░░░░░░▒▒░░░░░░░░░░░▒▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓████████████████████
|
||||
█████▒▒▒▒░░░░░░░░░▒▒▒░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░▒▒▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓████████████████████████
|
||||
█████▒▒▒▒░░░░░▒▒░▒▒▒▒░░░░░░░▒▒░░░░░░░░▒▒░░░░░░░░░░▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓████████████████████████████
|
||||
█████▒▒▒▒░░░░░▒▒░▒▒▒▒░░░░░▒▒░░░░░░░░░░▒▒▒░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████████████████████
|
||||
█████▒▒▒▒░░░░▒▒▒░▒▒▒▒░░░░▒▒░░░░░░░░░░░▒▒░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓██████████████████████████████
|
||||
█████▓▒▒▒▒▒░░▒▒▒▒▒▒▒▒░░░░▒▒░░░░░░░░░░▒▒▒░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒███████████████████████████████
|
||||
██████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▒░░░░░░▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓████████████████████████████████
|
||||
████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓██████████████████████████████████
|
||||
██████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒███████████████████████████████████████
|
||||
███████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒███████████████████████████████████████
|
||||
███████████▓▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████████████████████████████████████████
|
||||
█████████████▓▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█████████████████████████████████████████
|
||||
███████████████████▓▒▒▒▒░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓██████████████████████████████████████████
|
||||
████████████████████▓▒▒▒▒░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▓█████████████████████████████████████████████
|
||||
█████████████████████▓▒▒▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒▓█████████████████████████████████████████████████████████
|
||||
███████████████████████▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒██████████████████████████████████████████████████████████████
|
||||
███████████████████████████▓▒▒▒▒▒▒▓█████████████████████████████████████████████████████████████████
|
||||
████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
@@ -2,7 +2,10 @@
|
||||
Base CLI class and utilities for NimbusFlow CLI.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from backend.db.connection import DatabaseConnection
|
||||
from backend.repositories import (
|
||||
MemberRepository,
|
||||
@@ -14,6 +17,19 @@ from backend.repositories import (
|
||||
)
|
||||
from backend.services.scheduling_service import SchedulingService
|
||||
|
||||
# Import Colors from interactive module for consistent styling
|
||||
try:
|
||||
from .interactive import Colors
|
||||
except ImportError:
|
||||
# Fallback colors if interactive module not available
|
||||
class Colors:
|
||||
RESET = '\033[0m'
|
||||
SUCCESS = '\033[1m\033[92m'
|
||||
WARNING = '\033[1m\033[93m'
|
||||
ERROR = '\033[1m\033[91m'
|
||||
CYAN = '\033[96m'
|
||||
DIM = '\033[2m'
|
||||
|
||||
|
||||
class CLIError(Exception):
|
||||
"""Custom exception for CLI-specific errors."""
|
||||
@@ -21,17 +37,105 @@ class CLIError(Exception):
|
||||
|
||||
|
||||
class NimbusFlowCLI:
|
||||
"""Main CLI application class."""
|
||||
"""Main CLI application class with database versioning."""
|
||||
|
||||
def __init__(self, db_path: str = "database6_accepts_and_declines.db"):
|
||||
"""Initialize CLI with database connection."""
|
||||
self.db_path = Path(__file__).parent.parent / db_path
|
||||
if not self.db_path.exists():
|
||||
raise CLIError(f"Database not found: {self.db_path}")
|
||||
def __init__(self, db_path: str = "database.db", create_version: bool = True):
|
||||
"""Initialize CLI with database connection, always using most recent version."""
|
||||
self.db_dir = Path(__file__).parent.parent / "db" / "sqlite"
|
||||
self.base_db_path = self.db_dir / db_path
|
||||
|
||||
# Always find and use the most recent database version
|
||||
self.db_path = self._get_most_recent_database()
|
||||
|
||||
if create_version:
|
||||
# Create a new version based on the most recent one
|
||||
self.db_path = self._create_versioned_database()
|
||||
|
||||
self.db = DatabaseConnection(self.db_path)
|
||||
self._init_repositories()
|
||||
|
||||
def _get_most_recent_database(self) -> Path:
|
||||
"""Get the most recent database version, or base database if no versions exist."""
|
||||
versions = self.list_database_versions()
|
||||
|
||||
if versions:
|
||||
# Return the most recent versioned database
|
||||
most_recent = versions[0] # Already sorted newest first
|
||||
return most_recent
|
||||
else:
|
||||
# No versions exist, use base database
|
||||
if not self.base_db_path.exists():
|
||||
raise CLIError(f"Base database not found: {self.base_db_path}")
|
||||
return self.base_db_path
|
||||
|
||||
def _create_versioned_database(self) -> Path:
|
||||
"""Create a versioned copy from the most recent database."""
|
||||
source_db = self.db_path # Use the most recent database as source
|
||||
|
||||
# Generate timestamp-based version
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
version = self._get_next_version_number()
|
||||
|
||||
versioned_name = f"database_v{version}_{timestamp}.db"
|
||||
versioned_path = self.db_dir / versioned_name
|
||||
|
||||
# Copy the most recent database to create the versioned copy
|
||||
shutil.copy2(source_db, versioned_path)
|
||||
|
||||
print(f"{Colors.SUCCESS}Created versioned database:{Colors.RESET} {Colors.CYAN}{versioned_name}{Colors.RESET}")
|
||||
print(f"{Colors.DIM}Based on: {source_db.name}{Colors.RESET}")
|
||||
return versioned_path
|
||||
|
||||
def _get_next_version_number(self) -> int:
|
||||
"""Get the next version number by checking existing versioned databases."""
|
||||
version_pattern = "database_v*_*.db"
|
||||
existing_versions = list(self.db_dir.glob(version_pattern))
|
||||
|
||||
if not existing_versions:
|
||||
return 1
|
||||
|
||||
# Extract version numbers from existing files
|
||||
versions = []
|
||||
for db_file in existing_versions:
|
||||
try:
|
||||
# Parse version from filename like "database_v123_20250828_143022.db"
|
||||
parts = db_file.stem.split('_')
|
||||
if len(parts) >= 2 and parts[1].startswith('v'):
|
||||
version_num = int(parts[1][1:]) # Remove 'v' prefix
|
||||
versions.append(version_num)
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
return max(versions) + 1 if versions else 1
|
||||
|
||||
def list_database_versions(self) -> list[Path]:
|
||||
"""List all versioned databases in chronological order."""
|
||||
version_pattern = "database_v*_*.db"
|
||||
versioned_dbs = list(self.db_dir.glob(version_pattern))
|
||||
|
||||
# Sort by modification time (newest first)
|
||||
return sorted(versioned_dbs, key=lambda x: x.stat().st_mtime, reverse=True)
|
||||
|
||||
def cleanup_old_versions(self, keep_latest: int = 5) -> int:
|
||||
"""Clean up old database versions, keeping only the latest N versions."""
|
||||
versions = self.list_database_versions()
|
||||
|
||||
if len(versions) <= keep_latest:
|
||||
return 0
|
||||
|
||||
versions_to_delete = versions[keep_latest:]
|
||||
deleted_count = 0
|
||||
|
||||
for db_path in versions_to_delete:
|
||||
try:
|
||||
db_path.unlink()
|
||||
deleted_count += 1
|
||||
print(f"{Colors.DIM}Deleted old version: {db_path.name}{Colors.RESET}")
|
||||
except OSError as e:
|
||||
print(f"{Colors.WARNING}⚠️ Could not delete {db_path.name}: {e}{Colors.RESET}")
|
||||
|
||||
return deleted_count
|
||||
|
||||
def _init_repositories(self):
|
||||
"""Initialize all repository instances."""
|
||||
self.member_repo = MemberRepository(self.db)
|
||||
|
||||
@@ -3,6 +3,7 @@ Interactive CLI interface for NimbusFlow.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -35,6 +36,13 @@ class Colors:
|
||||
ERROR = '\033[1m\033[91m' # Bold Red
|
||||
WARNING = '\033[1m\033[93m' # Bold Yellow
|
||||
INPUT_BOX = '\033[90m' # Grey
|
||||
|
||||
# Gold shimmer colors
|
||||
GOLD_DARK = '\033[38;5;130m' # Dark gold
|
||||
GOLD_MEDIUM = '\033[38;5;178m' # Medium gold
|
||||
GOLD_BRIGHT = '\033[38;5;220m' # Bright gold
|
||||
GOLD_SHINE = '\033[1m\033[38;5;226m' # Bright shining gold
|
||||
GOLD_WHITE = '\033[1m\033[97m' # Bright white for peak shine
|
||||
|
||||
|
||||
def create_input_box(prompt: str, width: int = 60) -> str:
|
||||
@@ -61,27 +69,77 @@ def clear_screen():
|
||||
print("\033[2J\033[H")
|
||||
|
||||
|
||||
def get_shimmer_color(position: int, shimmer_center: int, shimmer_width: int = 8) -> str:
|
||||
"""Get the appropriate shimmer color based on distance from shimmer center."""
|
||||
distance = abs(position - shimmer_center)
|
||||
|
||||
if distance == 0:
|
||||
return Colors.GOLD_WHITE
|
||||
elif distance == 1:
|
||||
return Colors.GOLD_SHINE
|
||||
elif distance <= 3:
|
||||
return Colors.GOLD_BRIGHT
|
||||
elif distance <= 5:
|
||||
return Colors.GOLD_MEDIUM
|
||||
elif distance <= shimmer_width:
|
||||
return Colors.GOLD_DARK
|
||||
else:
|
||||
return Colors.GOLD_DARK
|
||||
|
||||
|
||||
def animate_nimbusflow_text() -> None:
|
||||
"""Animate the NimbusFlow ASCII text and frame with a gold shimmer effect."""
|
||||
# Complete welcome screen lines including borders
|
||||
welcome_lines = [
|
||||
"╔════════════════════════════════════════════════════════════════════════════════════════════╗",
|
||||
"║ ║",
|
||||
"║ ███╗ ██╗██╗███╗ ███╗██████╗ ██╗ ██╗███████╗ ███████╗██╗ ██████╗ ██╗ ██╗ ║",
|
||||
"║ ████╗ ██║██║████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔════╝██║ ██╔═══██╗██║ ██║ ║",
|
||||
"║ ██╔██╗ ██║██║██╔████╔██║██████╔╝██║ ██║███████╗ █████╗ ██║ ██║ ██║██║ █╗ ██║ ║",
|
||||
"║ ██║╚██╗██║██║██║╚██╔╝██║██╔══██╗██║ ██║╚════██║ ██╔══╝ ██║ ██║ ██║██║███╗██║ ║",
|
||||
"║ ██║ ╚████║██║██║ ╚═╝ ██║██████╔╝╚██████╔╝███████║ ██║ ███████╗╚██████╔╝╚███╔███╔╝ ║",
|
||||
"║ ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝ ║",
|
||||
"║ ║",
|
||||
"╚════════════════════════════════════════════════════════════════════════════════════════════╝"
|
||||
]
|
||||
|
||||
# Calculate max width for animation
|
||||
max_width = max(len(line) for line in welcome_lines)
|
||||
|
||||
# Animation parameters
|
||||
shimmer_width = 12
|
||||
total_steps = max_width + shimmer_width * 2
|
||||
step_delay = 0.025 # Seconds between frames (even faster animation)
|
||||
|
||||
# Animate the shimmer effect
|
||||
for step in range(total_steps):
|
||||
shimmer_center = step - shimmer_width
|
||||
|
||||
# Move cursor up to overwrite previous frame (10 lines total)
|
||||
if step > 0:
|
||||
print(f"\033[{len(welcome_lines)}A", end="")
|
||||
|
||||
for line in welcome_lines:
|
||||
for i, char in enumerate(line):
|
||||
if char.isspace():
|
||||
print(char, end="")
|
||||
else:
|
||||
color = get_shimmer_color(i, shimmer_center, shimmer_width)
|
||||
print(f"{color}{char}{Colors.RESET}", end="")
|
||||
|
||||
print() # New line after each row
|
||||
|
||||
# Add a small delay for animation
|
||||
time.sleep(step_delay)
|
||||
|
||||
|
||||
|
||||
|
||||
def display_welcome():
|
||||
"""Display welcome screen."""
|
||||
"""Display welcome screen with animated shimmer effect."""
|
||||
print("\033[2J\033[H") # Clear screen and move cursor to top
|
||||
|
||||
# NimbusFlow branding
|
||||
welcome_text = """
|
||||
╔════════════════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ███╗ ██╗██╗███╗ ███╗██████╗ ██╗ ██╗███████╗ ███████╗██╗ ██████╗ ██╗ ██╗ ║
|
||||
║ ████╗ ██║██║████╗ ████║██╔══██╗██║ ██║██╔════╝ ██╔════╝██║ ██╔═══██╗██║ ██║ ║
|
||||
║ ██╔██╗ ██║██║██╔████╔██║██████╔╝██║ ██║███████╗ █████╗ ██║ ██║ ██║██║ █╗ ██║ ║
|
||||
║ ██║╚██╗██║██║██║╚██╔╝██║██╔══██╗██║ ██║╚════██║ ██╔══╝ ██║ ██║ ██║██║███╗██║ ║
|
||||
║ ██║ ╚████║██║██║ ╚═╝ ██║██████╔╝╚██████╔╝███████║ ██║ ███████╗╚██████╔╝╚███╔███╔╝ ║
|
||||
║ ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝ ║
|
||||
║ ║
|
||||
║ 🎵 Scheduling System 🎵 ║
|
||||
╚════════════════════════════════════════════════════════════════════════════════════════════╝
|
||||
"""
|
||||
print(welcome_text)
|
||||
print() # Add some top padding
|
||||
animate_nimbusflow_text()
|
||||
print()
|
||||
|
||||
|
||||
@@ -355,7 +413,7 @@ def run_interactive_mode(cli: "NimbusFlowCLI"):
|
||||
display_welcome()
|
||||
|
||||
print(f"{Colors.HEADER}Welcome to the NimbusFlow Interactive CLI{Colors.RESET}")
|
||||
print(f"{Colors.DIM}Navigate through menus to manage your choir scheduling system.{Colors.RESET}")
|
||||
print(f"{Colors.DIM}Navigate through menus to manage your scheduling system.{Colors.RESET}")
|
||||
print()
|
||||
input(f"{Colors.DIM}Press Enter to continue...{Colors.RESET}")
|
||||
|
||||
|
||||
@@ -44,16 +44,30 @@ def main():
|
||||
if not args.command:
|
||||
# Launch interactive mode when no command is provided
|
||||
try:
|
||||
cli = NimbusFlowCLI()
|
||||
cli = NimbusFlowCLI(create_version=True) # Always create versioned DB for interactive mode
|
||||
|
||||
# Show versioning info with colors
|
||||
from .base import Colors
|
||||
versions = cli.list_database_versions()
|
||||
if len(versions) > 1:
|
||||
print(f"{Colors.CYAN}Database versions available:{Colors.RESET} {Colors.SUCCESS}{len(versions)}{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}Using:{Colors.RESET} {Colors.CYAN}{cli.db_path.name}{Colors.RESET}")
|
||||
|
||||
# Auto-cleanup if too many versions
|
||||
if len(versions) > 10:
|
||||
deleted = cli.cleanup_old_versions(keep_latest=5)
|
||||
if deleted > 0:
|
||||
print(f"{Colors.WARNING}Cleaned up {deleted} old database versions{Colors.RESET}")
|
||||
|
||||
run_interactive_mode(cli)
|
||||
except CLIError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
print(f"{Colors.ERROR}❌ Error: {e}{Colors.RESET}")
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 Interrupted by user")
|
||||
print(f"\n{Colors.WARNING}🛑 Interrupted by user{Colors.RESET}")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"❌ Unexpected error: {e}")
|
||||
print(f"{Colors.ERROR}❌ Unexpected error: {e}{Colors.RESET}")
|
||||
return 1
|
||||
finally:
|
||||
if 'cli' in locals():
|
||||
@@ -61,7 +75,11 @@ def main():
|
||||
return
|
||||
|
||||
try:
|
||||
cli = NimbusFlowCLI()
|
||||
cli = NimbusFlowCLI(create_version=False) # Don't version for regular CLI commands
|
||||
|
||||
# Show which database is being used for regular commands
|
||||
from .base import Colors
|
||||
print(f"{Colors.CYAN}Using database:{Colors.RESET} {Colors.CYAN}{cli.db_path.name}{Colors.RESET}")
|
||||
|
||||
# Route commands to their respective handlers
|
||||
if args.command == "members":
|
||||
@@ -70,7 +88,7 @@ def main():
|
||||
elif args.members_action == "show":
|
||||
cmd_members_show(cli, args)
|
||||
else:
|
||||
print("❌ Unknown members action. Use 'list' or 'show'")
|
||||
print(f"{Colors.ERROR}❌ Unknown members action. Use 'list' or 'show'{Colors.RESET}")
|
||||
|
||||
elif args.command == "schedules":
|
||||
if args.schedules_action == "list":
|
||||
@@ -84,25 +102,25 @@ def main():
|
||||
elif args.schedules_action == "schedule":
|
||||
cmd_schedules_schedule(cli, args)
|
||||
else:
|
||||
print("❌ Unknown schedules action. Use 'list', 'show', 'accept', 'decline', or 'schedule'")
|
||||
print(f"{Colors.ERROR}❌ Unknown schedules action. Use 'list', 'show', 'accept', 'decline', or 'schedule'{Colors.RESET}")
|
||||
|
||||
elif args.command == "services":
|
||||
if args.services_action == "list":
|
||||
cmd_services_list(cli, args)
|
||||
else:
|
||||
print("❌ Unknown services action. Use 'list'")
|
||||
print(f"{Colors.ERROR}❌ Unknown services action. Use 'list'{Colors.RESET}")
|
||||
|
||||
else:
|
||||
print(f"❌ Unknown command: {args.command}")
|
||||
print(f"{Colors.ERROR}❌ Unknown command: {args.command}{Colors.RESET}")
|
||||
|
||||
except CLIError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
print(f"{Colors.ERROR}❌ Error: {e}{Colors.RESET}")
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 Interrupted by user")
|
||||
print(f"\n{Colors.WARNING}🛑 Interrupted by user{Colors.RESET}")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"❌ Unexpected error: {e}")
|
||||
print(f"{Colors.ERROR}❌ Unexpected error: {e}{Colors.RESET}")
|
||||
return 1
|
||||
finally:
|
||||
if 'cli' in locals():
|
||||
|
||||
Reference in New Issue
Block a user