First commit
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
"""
|
||||
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.")
|
||||
Reference in New Issue
Block a user