286 lines
9.5 KiB
Python
286 lines
9.5 KiB
Python
"""
|
|
Main Window
|
|
|
|
Main GUI window that integrates all frames.
|
|
"""
|
|
|
|
import customtkinter as ctk
|
|
from ui.login_frame import LoginFrame
|
|
from ui.subscription_selection_frame import SubscriptionSelectionFrame
|
|
from ui.app_selection_frame import AppSelectionFrame
|
|
from ui.secret_generation_frame import SecretGenerationFrame
|
|
from ui.result_frame import ResultFrame
|
|
from typing import Callable, Dict, List
|
|
|
|
|
|
class MainWindow(ctk.CTk):
|
|
"""Main application window."""
|
|
|
|
def __init__(
|
|
self,
|
|
on_connect: Callable = None,
|
|
on_subscription_selected: Callable = None,
|
|
on_app_selected: Callable = None,
|
|
on_generate_secret: Callable = None,
|
|
on_generate_another: Callable = None
|
|
):
|
|
"""
|
|
Initialize the main window.
|
|
|
|
Args:
|
|
on_connect: Callback for authentication
|
|
on_subscription_selected: Callback when subscription is selected
|
|
on_app_selected: Callback when app is selected
|
|
on_generate_secret: Callback when generate secret is clicked
|
|
on_generate_another: Callback when generate another is clicked
|
|
"""
|
|
super().__init__()
|
|
|
|
# Configure window
|
|
self.title("Azure Key Vault Secret Manager")
|
|
self.geometry("950x850")
|
|
|
|
# Set application icon (if available)
|
|
self._set_icon()
|
|
|
|
# Configure grid
|
|
self.grid_columnconfigure(0, weight=1)
|
|
self.grid_rowconfigure(1, weight=1)
|
|
|
|
# Title
|
|
self.title_label = ctk.CTkLabel(
|
|
self,
|
|
text="Azure Key Vault Secret Manager",
|
|
font=ctk.CTkFont(size=28, weight="bold")
|
|
)
|
|
self.title_label.grid(row=0, column=0, padx=20, pady=20, sticky="n")
|
|
|
|
# Scrollable frame for content with optimized scrolling
|
|
self.scroll_frame = ctk.CTkScrollableFrame(
|
|
self,
|
|
scrollbar_button_color=("gray75", "gray25"),
|
|
scrollbar_button_hover_color=("gray65", "gray35")
|
|
)
|
|
self.scroll_frame.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="nsew")
|
|
self.scroll_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
# Optimize scroll step for smoother scrolling (40px = 2x faster than default 20px)
|
|
self.scroll_frame._parent_canvas.configure(yscrollincrement=40)
|
|
|
|
# Add smooth mouse wheel scrolling
|
|
def smooth_scroll(event):
|
|
"""Handle smooth mouse wheel scrolling."""
|
|
# Platform-specific delta handling
|
|
if event.delta > 0:
|
|
delta = -1 # Scroll up
|
|
else:
|
|
delta = 1 # Scroll down
|
|
|
|
self.scroll_frame._parent_canvas.yview_scroll(delta, "units")
|
|
return "break" # Prevent event propagation
|
|
|
|
# Bind mouse wheel event (Windows/Mac)
|
|
self.scroll_frame._parent_canvas.bind_all("<MouseWheel>", smooth_scroll, add="+")
|
|
|
|
# Login Frame
|
|
self.login_frame = LoginFrame(self.scroll_frame, on_connect=on_connect)
|
|
self.login_frame.grid(row=0, column=0, padx=0, pady=(0, 15), sticky="ew")
|
|
|
|
# Subscription Selection Frame
|
|
self.subscription_selection_frame = SubscriptionSelectionFrame(
|
|
self.scroll_frame,
|
|
on_subscription_selected=on_subscription_selected
|
|
)
|
|
self.subscription_selection_frame.grid(row=1, column=0, padx=0, pady=(0, 15), sticky="ew")
|
|
self.subscription_selection_frame.set_enabled(False)
|
|
|
|
# App Selection Frame
|
|
self.app_selection_frame = AppSelectionFrame(
|
|
self.scroll_frame,
|
|
on_app_selected=on_app_selected
|
|
)
|
|
self.app_selection_frame.grid(row=2, column=0, padx=0, pady=(0, 15), sticky="ew")
|
|
self.app_selection_frame.set_enabled(False)
|
|
|
|
# Secret Generation Frame
|
|
self.secret_generation_frame = SecretGenerationFrame(
|
|
self.scroll_frame,
|
|
on_generate=on_generate_secret
|
|
)
|
|
self.secret_generation_frame.grid(row=3, column=0, padx=0, pady=(0, 15), sticky="ew")
|
|
self.secret_generation_frame.set_enabled(False)
|
|
|
|
# Result Frame (initially hidden)
|
|
self.result_frame = ResultFrame(
|
|
self.scroll_frame,
|
|
on_generate_another=on_generate_another
|
|
)
|
|
self.result_frame.grid(row=4, column=0, padx=0, pady=(0, 15), sticky="ew")
|
|
self.result_frame.hide()
|
|
|
|
def set_authenticated(self, authenticated: bool):
|
|
"""
|
|
Update UI after authentication.
|
|
|
|
Args:
|
|
authenticated: Whether authentication succeeded
|
|
"""
|
|
self.login_frame.set_authenticated(authenticated)
|
|
|
|
if authenticated:
|
|
self.subscription_selection_frame.set_enabled(True)
|
|
|
|
def set_connecting(self):
|
|
"""Set UI to connecting state."""
|
|
self.login_frame.set_connecting()
|
|
|
|
def enable_connect_button(self):
|
|
"""Enable connect button for retry."""
|
|
self.login_frame.enable_button()
|
|
|
|
def set_subscriptions(self, subscriptions: List[Dict[str, str]]):
|
|
"""
|
|
Set the list of subscriptions.
|
|
|
|
Args:
|
|
subscriptions: List of subscription dictionaries
|
|
"""
|
|
self.subscription_selection_frame.set_subscriptions(subscriptions)
|
|
|
|
def set_subscription_selected(self):
|
|
"""Enable UI after subscription is selected."""
|
|
self.app_selection_frame.set_enabled(True)
|
|
self.secret_generation_frame.set_enabled(True)
|
|
|
|
def set_apps(self, apps: List[Dict[str, str]]):
|
|
"""
|
|
Set the list of applications.
|
|
|
|
Args:
|
|
apps: List of app dictionaries
|
|
"""
|
|
self.app_selection_frame.set_apps(apps)
|
|
|
|
def set_vaults(self, vaults: List[Dict[str, str]]):
|
|
"""
|
|
Set the list of Key Vaults.
|
|
|
|
Args:
|
|
vaults: List of vault dictionaries
|
|
"""
|
|
self.secret_generation_frame.set_vaults(vaults)
|
|
|
|
def set_loading_subscriptions(self, loading: bool):
|
|
"""
|
|
Set loading subscriptions state.
|
|
|
|
Args:
|
|
loading: Whether currently loading
|
|
"""
|
|
self.subscription_selection_frame.set_loading(loading)
|
|
|
|
def set_loading_apps(self, loading: bool):
|
|
"""
|
|
Set loading apps state.
|
|
|
|
Args:
|
|
loading: Whether currently loading
|
|
"""
|
|
self.app_selection_frame.set_loading(loading)
|
|
|
|
def set_loading_vaults(self, loading: bool):
|
|
"""
|
|
Set loading vaults state.
|
|
|
|
Args:
|
|
loading: Whether currently loading
|
|
"""
|
|
self.secret_generation_frame.set_loading_vaults(loading)
|
|
|
|
def set_generating(self, generating: bool):
|
|
"""
|
|
Set generating secret state.
|
|
|
|
Args:
|
|
generating: Whether currently generating
|
|
"""
|
|
self.secret_generation_frame.set_generating(generating)
|
|
|
|
def show_result(
|
|
self,
|
|
secret_name: str,
|
|
vault_name: str,
|
|
secret_value: str,
|
|
removed_count: int = 0
|
|
):
|
|
"""
|
|
Show secret generation result.
|
|
|
|
Args:
|
|
secret_name: The sanitized secret name
|
|
vault_name: The Key Vault name
|
|
secret_value: The secret value
|
|
removed_count: Number of old secrets removed
|
|
"""
|
|
self.result_frame.show_result(secret_name, vault_name, secret_value, removed_count)
|
|
|
|
def reset_form(self):
|
|
"""Reset the secret generation form."""
|
|
self.secret_generation_frame.reset()
|
|
self.result_frame.hide()
|
|
|
|
def get_selected_app(self) -> Dict[str, str]:
|
|
"""Get the currently selected app."""
|
|
return self.app_selection_frame.get_selected_app()
|
|
|
|
def get_description(self) -> str:
|
|
"""Get the secret description."""
|
|
return self.secret_generation_frame.get_description()
|
|
|
|
def get_selected_vault(self) -> Dict[str, str]:
|
|
"""Get the currently selected vault."""
|
|
return self.secret_generation_frame.get_selected_vault()
|
|
|
|
def get_remove_old_secrets(self) -> bool:
|
|
"""Get whether to remove old secrets."""
|
|
return self.secret_generation_frame.get_remove_old_secrets()
|
|
|
|
def _set_icon(self):
|
|
"""Set the application icon if available."""
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Get the directory where the script is located
|
|
script_dir = Path(__file__).parent.parent
|
|
|
|
# Try common icon file names and locations
|
|
icon_paths = [
|
|
script_dir / "icon.ico",
|
|
script_dir / "assets" / "icon.ico",
|
|
script_dir / "icon.png",
|
|
script_dir / "assets" / "icon.png",
|
|
]
|
|
|
|
for icon_path in icon_paths:
|
|
if icon_path.exists():
|
|
try:
|
|
if icon_path.suffix == '.ico':
|
|
# Use .ico file directly (Windows)
|
|
self.iconbitmap(str(icon_path))
|
|
print(f"Icon loaded: {icon_path}")
|
|
return
|
|
elif icon_path.suffix == '.png':
|
|
# Use .png file with iconphoto (cross-platform)
|
|
import tkinter as tk
|
|
from PIL import Image, ImageTk
|
|
img = Image.open(icon_path)
|
|
photo = ImageTk.PhotoImage(img)
|
|
self.iconphoto(True, photo)
|
|
print(f"Icon loaded: {icon_path}")
|
|
return
|
|
except Exception as e:
|
|
print(f"Failed to load icon from {icon_path}: {str(e)}")
|
|
|
|
# No icon found - use default
|
|
print("No custom icon found. Using default application icon.")
|