"""Complete Modal Dashboard with all authentication options and enhanced features.""" import asyncio import json import threading import webbrowser from datetime import datetime from pathlib import Path from typing import Dict, List, Optional, Tuple import gradio as gr from loguru import logger from rich import print as rprint # Try to use uvloop for better performance try: import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) print("โœ… Using uvloop for async operations") except ImportError: print("๐Ÿ“ uvloop not available, using default asyncio") # Import Modal's actual token flow try: from modal._utils.grpc_utils import retry_transient_errors from modal._utils.http_utils import run_temporary_http_server from modal.client import _Client from modal_proto import api_pb2 print("โœ… Modal imports successful") except ImportError as e: print(f"โŒ Modal import failed: {e}") print("Install with: pip install modal") from modal_for_noobs.auth_manager import ModalAuthConfig, ModalAuthManager from modal_for_noobs.cli_helpers.common import MODAL_DARK_GREEN, MODAL_GREEN, MODAL_LIGHT_GREEN from modal_for_noobs.modal_deploy import ModalDeployer from modal_for_noobs.template_generator import generate_from_wizard_input # Create a global event loop for Modal operations modal_loop = None modal_thread = None def get_modal_loop(): """Get or create the global Modal event loop.""" global modal_loop, modal_thread if modal_loop is None or modal_loop.is_closed(): modal_loop = asyncio.new_event_loop() # Run the loop in a separate thread def run_loop(): asyncio.set_event_loop(modal_loop) modal_loop.run_forever() modal_thread = threading.Thread(target=run_loop, daemon=True) modal_thread.start() return modal_loop class CompleteModalDashboard: """Complete Modal dashboard with all authentication options and enhanced features.""" def __init__(self): """Initialize the complete dashboard.""" self.auth_manager = ModalAuthManager() self.is_authenticated = False self.current_auth_config = None def create_complete_interface(self) -> gr.Blocks: """Create the complete dashboard interface with all features. Returns: Gradio Blocks interface with all authentication and deployment features """ # Custom CSS with all three greens custom_css = f""" .modal-header {{ background: linear-gradient(135deg, {MODAL_GREEN} 0%, {MODAL_DARK_GREEN} 50%, {MODAL_LIGHT_GREEN} 100%); color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px; text-align: center; }} .auth-card {{ border: 2px solid {MODAL_GREEN}; border-radius: 8px; padding: 16px; margin: 8px 0; background: linear-gradient(45deg, rgba(16, 185, 129, 0.05) 0%, rgba(5, 150, 105, 0.05) 100%); }} .auth-success {{ background: linear-gradient(45deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%); border-color: {MODAL_GREEN}; color: {MODAL_DARK_GREEN}; }} .auth-pending {{ background: linear-gradient(45deg, rgba(59, 130, 246, 0.1) 0%, rgba(37, 99, 235, 0.1) 100%); border-color: #3b82f6; color: #1d4ed8; }} .wizard-step {{ background: linear-gradient(135deg, {MODAL_GREEN} 0%, {MODAL_DARK_GREEN} 100%); color: white; padding: 12px 20px; border-radius: 8px; margin: 8px 0; font-weight: bold; }} .feature-card {{ border: 1px solid {MODAL_LIGHT_GREEN}; border-radius: 8px; padding: 16px; margin: 8px 0; background: linear-gradient(45deg, rgba(52, 211, 153, 0.05) 0%, rgba(16, 185, 129, 0.05) 100%); }} """ with gr.Blocks( title="Modal for Noobs - Complete Dashboard", css=custom_css, theme=gr.themes.Soft( primary_hue="green", secondary_hue="emerald", neutral_hue="slate" ) ) as dashboard: # Header with all three greens gr.HTML(f""" """) # Global state auth_state = gr.State(value={ "authenticated": False, "method": None, "config": None, "session_id": None }) with gr.Tabs() as main_tabs: # Tab 1: Authentication Hub (Primary) with gr.Tab("๐Ÿ” Connect to Modal") as auth_tab: gr.Markdown("## Welcome! Let's get you connected to Modal") gr.Markdown( f"Choose your preferred authentication method. **Link Authentication** is " f"recommended for beginners - it's the easiest and most secure!" ) # Authentication status auth_status_display = gr.HTML( value=self._get_auth_status_html(False), elem_id="auth-status-main" ) with gr.Tabs() as auth_methods: # Method 1: Link Authentication (Primary - Noob Friendly) with gr.Tab("๐ŸŒ Link Authentication (Recommended)") as link_auth_tab: with gr.Column(elem_classes=["auth-card"]): gr.Markdown("### ๐ŸŽฏ Super Easy - Just Click and Authorize!") gr.Markdown( "This is the **easiest way** to connect. No API keys to copy, " "no complex setup - just click a button and authorize in your browser!" ) # Link auth interface with gr.Row(): with gr.Column(scale=2): gr.Markdown( "**How it works:**\\n" "1. ๐Ÿ”— Click the button below\\n" "2. ๐ŸŒ A new tab opens with Modal's authorization page\\n" "3. ๐Ÿ” Log in or sign up (it's free!)\\n" "4. โœ… Click 'Authorize' and you're done!" ) with gr.Column(scale=1): link_auth_btn = gr.Button( "๐Ÿš€ Connect via Link", variant="primary", size="lg", elem_classes=["feature-card"] ) # 3 AUTHENTICATION BUTTONS (like debug dashboard) with gr.Column() as link_details: gr.Markdown("### ๐ŸŽฎ Authentication Controls") with gr.Row(): initialize_btn = gr.Button( "๐Ÿš€ 1. Initialize Modal Client", variant="primary", scale=1 ) start_auth_btn = gr.Button( "๐Ÿ” 2. Start Auth Flow", variant="primary", scale=1 ) finish_auth_btn = gr.Button( "โœ… 3. Finish Auth", variant="primary", scale=1 ) # Authentication details (textboxes) with gr.Column() as auth_details: gr.Markdown("### ๐Ÿ“‹ Authentication Details") with gr.Row(): with gr.Column(): flow_id_display = gr.Textbox( label="Token Flow ID", value="", interactive=False ) auth_url_display = gr.Textbox( label="Modal Authentication URL", value="", interactive=False ) auth_code_display = gr.Textbox( label="Authentication Code", value="", interactive=False ) # Progress section for link auth with gr.Column(visible=False) as link_progress: gr.Markdown("### ๐Ÿ”„ Waiting for authorization...") gr.Markdown("A new tab should have opened. Please authorize modal-for-noobs to continue.") link_progress_bar = gr.HTML( value=self._get_progress_html(0), elem_id="link-progress" ) link_cancel_btn = gr.Button("Check Authorization", variant="primary") # Success section for link auth with gr.Column(visible=False) as link_success: gr.Markdown("### โœ… Connected Successfully!") link_workspace_display = gr.Textbox( label="Connected Workspace", value="", interactive=False ) gr.Markdown("๐ŸŽ‰ You're all set! You can now deploy your apps to Modal!") # Method 2: Token Authentication (Manual) with gr.Tab("๐Ÿ”‘ Token Authentication") as token_auth_tab: with gr.Column(elem_classes=["auth-card"]): gr.Markdown("### ๐Ÿ› ๏ธ Manual Setup - For Advanced Users") gr.Markdown( "If you prefer manual setup or already have tokens, enter them here." ) with gr.Row(): with gr.Column(scale=2): gr.Markdown( "**Steps:**\\n" "1. ๐ŸŒ Get your tokens from Modal\\n" "2. ๐Ÿ“‹ Copy Token ID and Token Secret\\n" "3. ๐Ÿ“ Paste them below\\n" "4. โœ… Click Authenticate" ) with gr.Column(scale=1): open_tokens_btn = gr.Button( "๐ŸŒ Open Tokens Page", variant="secondary", elem_classes=["feature-card"] ) with gr.Row(): token_id_input = gr.Textbox( label="Token ID (starts with 'ak-')", placeholder="ak-your-token-id-here", type="text", scale=1 ) token_secret_input = gr.Textbox( label="Token Secret (starts with 'as-')", placeholder="as-your-token-secret-here", type="password", scale=1 ) workspace_input = gr.Textbox( label="Workspace Name (optional)", placeholder="your-workspace-name" ) with gr.Row(): token_auth_btn = gr.Button( "๐Ÿ” Authenticate with Tokens", variant="primary", scale=1 ) test_connection_btn = gr.Button( "๐Ÿงช Test Connection", variant="secondary", scale=1 ) token_result = gr.Textbox( label="Result", interactive=False, lines=3 ) # Method 3: File Upload Authentication with gr.Tab("๐Ÿ“ File Upload") as file_auth_tab: with gr.Column(elem_classes=["auth-card"]): gr.Markdown("### ๐Ÿ“‚ Import from File") gr.Markdown( "If you have a Modal config file from another setup, upload it here." ) config_file_upload = gr.File( label="Upload Modal Config File", file_types=[".toml", ".json", ".txt"], type="filepath" ) file_result = gr.Textbox( label="Upload Result", interactive=False, lines=3 ) gr.Markdown( "**Supported formats:**\\n" "- JSON files with token_id and token_secret\\n" "- TOML configuration files\\n" "- Text files with environment variables" ) # Help section with gr.Accordion("โ“ Need Help?", open=False): gr.Markdown( f""" ### ๐Ÿ†˜ Authentication Help **Which method should I choose?** - ๐ŸŒ **Link Authentication**: Best for beginners, most secure, easiest setup - ๐Ÿ”‘ **Token Authentication**: For advanced users who prefer manual control - ๐Ÿ“ **File Upload**: For importing existing configurations ### ๐Ÿ”— Link Authentication (Recommended) This is the safest and easiest method. It uses OAuth 2.0 (same as "Sign in with Google"). Your password is never shared with modal-for-noobs. ### ๐Ÿ›ก๏ธ Security - Link authentication is most secure (OAuth 2.0) - Token secrets are never saved to disk - All connections use HTTPS encryption ### ๐Ÿ†˜ Troubleshooting - **Browser tab didn't open?** Check your popup blocker - **Authorization failed?** Make sure you have a Modal account - **Still having issues?** Try the Token Authentication method """ ) # Tab 2: App Generator with Enhanced Features with gr.Tab("๐ŸŽฏ Generate Your App") as generate_tab: with gr.Column(): # Auth check for this tab gen_auth_check = gr.HTML( value=self._get_auth_check_html(False), elem_id="auth-check-generate" ) with gr.Column() as generator_section: gr.Markdown("## ๐Ÿง™โ€โ™‚๏ธ Enhanced App Generation Wizard") gr.Markdown( "Create powerful Modal deployments with our advanced wizard. " "All features from modal_generate are included!" ) # File upload app_file_upload = gr.File( label="๐Ÿ“ Upload Your Python App", file_types=[".py"], type="filepath" ) # Or paste code gr.Markdown("**Or paste your code here:**") code_input = gr.Code( label="Python Code", language="python", value="""# Paste your Gradio app code here... import gradio as gr def greet(name): return f'Hello {name}!' demo = gr.Interface(fn=greet, inputs='text', outputs='text') demo.launch()""", lines=10 ) # Enhanced wizard sections with gr.Accordion("โš™๏ธ Enhanced Configuration Wizard", open=True): # Step 1: Basic Settings with gr.Group(): gr.HTML('
Step 1: Basic Settings
') with gr.Row(): app_name_input = gr.Textbox( label="App Name", placeholder="my-awesome-app", scale=2 ) deployment_mode = gr.Dropdown( label="Deployment Mode", choices=[ ("๐ŸŒฑ Minimum - Fast & cheap", "minimum"), ("โšก Optimized - GPU + ML", "optimized"), ("๐Ÿ““ Marimo - Reactive notebooks", "marimo"), ("๐Ÿช Gradio+Jupyter - Classic notebooks", "gradio-jupyter"), ], value="minimum", scale=2 ) # Step 2: Advanced Features (Enhanced) with gr.Group(): gr.HTML('
Step 2: Advanced Features
') with gr.Row(): enable_gpu = gr.Checkbox( label="๐Ÿš€ Enable GPU", value=False ) gpu_type = gr.Dropdown( label="GPU Type", choices=["any", "T4", "L4", "A10G", "A100", "H100"], value="any", visible=False ) num_gpus = gr.Slider( label="Number of GPUs", minimum=1, maximum=8, value=1, step=1, visible=False ) with gr.Row(): enable_storage = gr.Checkbox( label="๐Ÿ’พ Persistent Storage (NFS)", value=False ) enable_logging = gr.Checkbox( label="๐Ÿ“ Enhanced Logging", value=False ) enable_dashboard_monitoring = gr.Checkbox( label="๐Ÿ“Š Dashboard Monitoring", value=False ) with gr.Row(): timeout_minutes = gr.Slider( label="โฑ๏ธ Timeout (minutes)", minimum=5, maximum=1440, # 24 hours value=60, step=5 ) max_containers = gr.Slider( label="๐Ÿ“ˆ Max Containers", minimum=1, maximum=100, value=10, step=1 ) # Step 3: Dependencies & Environment with gr.Group(): gr.HTML('
Step 3: Dependencies & Environment
') requirements_upload = gr.File( label="๐Ÿ“ฆ Upload requirements.txt", file_types=[".txt"], type="filepath" ) with gr.Row(): python_packages = gr.Textbox( label="Extra Python Packages", placeholder="numpy, pandas, scikit-learn", info="Comma-separated list" ) system_packages = gr.Textbox( label="System Dependencies", placeholder="ffmpeg, curl, git", info="Comma-separated list" ) # Environment variables with gr.Row(): env_vars_input = gr.Textbox( label="Environment Variables", placeholder="DEBUG=true,API_URL=https://api.example.com", info="Comma-separated key=value pairs" ) secrets_input = gr.Textbox( label="Modal Secrets", placeholder="api-key,db-password", info="Comma-separated secret names" ) # Step 4: Remote Functions (Enhanced Feature) with gr.Group(): gr.HTML('
Step 4: Remote Functions (Advanced)
') enable_remote_functions = gr.Checkbox( label="๐Ÿ”ง Enable Remote Functions", value=False, info="Add background tasks and scheduled functions" ) with gr.Column(visible=False) as remote_functions_config: gr.Markdown("**Configure Remote Functions:**") remote_function_name = gr.Textbox( label="Function Name", placeholder="process_data" ) with gr.Row(): remote_gpu = gr.Dropdown( label="Function GPU", choices=["none", "T4", "A100", "H100"], value="none" ) keep_warm = gr.Slider( label="Keep Warm Containers", minimum=0, maximum=10, value=0, step=1 ) with gr.Row(): schedule_input = gr.Textbox( label="Schedule (cron format)", placeholder="0 */6 * * *", info="Optional: Schedule function to run periodically" ) remote_timeout = gr.Slider( label="Function Timeout (seconds)", minimum=60, maximum=3600, value=300, step=60 ) add_remote_function_btn = gr.Button( "โž• Add Remote Function", variant="secondary", size="sm" ) remote_functions_list = gr.JSON( label="Configured Remote Functions", value=[], visible=False ) # Generate button generate_btn = gr.Button( "๐Ÿš€ Generate Enhanced Deployment", variant="primary", size="lg", elem_classes=["feature-card"] ) # Output generation_output = gr.Code( label="Generated Deployment Code", language="python", lines=25, visible=False ) with gr.Row(visible=False) as output_actions: download_btn = gr.DownloadButton( label="๐Ÿ“ฅ Download Deployment File", variant="secondary" ) deploy_btn = gr.Button( "๐Ÿš€ Deploy to Modal", variant="primary" ) # Tab 3: Monitor & Manage with gr.Tab("๐Ÿ“Š Monitor & Manage") as monitor_tab: with gr.Column(): # Auth check monitor_auth_check = gr.HTML( value=self._get_auth_check_html(False), elem_id="auth-check-monitor" ) with gr.Column() as monitoring_section: gr.Markdown("## ๐Ÿ“Š Deployment Monitoring & Management") with gr.Row(): refresh_btn = gr.Button("๐Ÿ”„ Refresh", variant="secondary") stop_selected_btn = gr.Button("๐Ÿ›‘ Stop Selected", variant="secondary") logs_btn = gr.Button("๐Ÿ“ View Logs", variant="secondary") # Deployments table deployments_df = gr.Dataframe( headers=["App Name", "Status", "URL", "Created", "GPU", "Cost"], datatype=["str", "str", "str", "str", "str", "str"], col_count=(6, "fixed"), label="Your Deployments" ) # Selected deployment details with gr.Row(): selected_app = gr.Dropdown( label="Select App for Details", choices=[], interactive=True ) with gr.Column(): get_info_btn = gr.Button("โ„น๏ธ Get Info") kill_app_btn = gr.Button("๐Ÿ’€ Kill App", variant="stop") # Logs output logs_output = gr.Textbox( label="Application Logs", lines=15, max_lines=15, visible=False, interactive=False ) # Tab 4: Help & Examples with gr.Tab("โ“ Help & Examples") as help_tab: gr.Markdown("## ๐ŸŽ“ Complete Guide to Modal for Noobs") with gr.Accordion("๐Ÿš€ Quick Start Guide", open=True): gr.Markdown(f""" ### Step-by-Step Guide **1. ๐Ÿ” Connect to Modal (Choose your method)** - **๐ŸŒ Link Authentication (Recommended)**: Just click and authorize! - **๐Ÿ”‘ Token Authentication**: Manual setup with API tokens - **๐Ÿ“ File Upload**: Import existing configuration **2. ๐ŸŽฏ Generate Your App** - Upload your Python file or paste code - Use the Enhanced Configuration Wizard - Configure GPU, storage, environment variables - Add remote functions for background tasks **3. ๐Ÿš€ Deploy and Monitor** - Review generated code - Deploy to Modal - Monitor in real-time - View logs and manage deployments """) with gr.Accordion("๐ŸŽฏ Example Applications", open=False): gr.Markdown(""" ### Simple Gradio App ```python import gradio as gr def greet(name): return f"Hello {name}!" demo = gr.Interface(fn=greet, inputs="text", outputs="text") demo.launch() ``` ### ML Model with GPU ```python import gradio as gr import torch def predict(text): # Your ML model here device = "cuda" if torch.cuda.is_available() else "cpu" return f"Processed on {device}: {text}" demo = gr.Interface(fn=predict, inputs="text", outputs="text") demo.launch() ``` ### Advanced App with Background Tasks ```python import gradio as gr from datetime import datetime def process_data(data): # Main interface function return f"Processed at {datetime.now()}: {data}" # Background task (will be added as remote function) def background_task(): print("Running background task...") return "Task completed" demo = gr.Interface(fn=process_data, inputs="text", outputs="text") demo.launch() ``` """) with gr.Accordion("๐Ÿ”ง Advanced Features Guide", open=False): gr.Markdown(f""" ### Enhanced Features Explained **๐Ÿš€ GPU Support** - Choose from T4, L4, A10G, A100, H100 - Configure number of GPUs (1-8) - Automatic CUDA setup **๐Ÿ’พ Persistent Storage** - NFS volumes for data persistence - Shared storage across containers - Automatic backup capabilities **๐Ÿ”ง Remote Functions** - Background tasks - Scheduled functions (cron) - Keep-warm containers - Custom GPU allocation per function **๐Ÿ“Š Monitoring & Logging** - Real-time deployment status - Enhanced logging with structured output - Performance metrics - Cost tracking **๐Ÿ” Security Features** - Environment variables - Modal secrets integration - Secure token handling - OAuth 2.0 authentication """) with gr.Accordion("๐Ÿ†˜ Troubleshooting", open=False): gr.Markdown(""" ### Common Issues & Solutions **Authentication Problems:** - Make sure you clicked "Authorize" in the browser - Check your popup blocker settings - Try refreshing and connecting again - Use Token Authentication as fallback **Deployment Fails:** - Verify your code has a `demo` variable for Gradio apps - Check all dependencies are included - Try "minimum" mode first - Review generated code for errors **App Won't Start:** - Check logs in the monitoring tab - Verify your code works locally - Check environment variables and secrets - Ensure GPU configuration matches your code **Performance Issues:** - Enable GPU for ML workloads - Increase container limits - Use keep-warm for frequently called functions - Monitor resource usage """) # Monitoring functions async def refresh_deployments(): """Refresh the deployments list from Modal.""" try: import subprocess import asyncio from datetime import datetime # Run modal app list command process = await asyncio.create_subprocess_exec( "modal", "app", "list", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await process.communicate() if process.returncode != 0: error_msg = stderr.decode() if stderr else "Unknown error" return gr.update(value=[["Error", "Failed to list apps", "", "", "", error_msg]]), [] # Parse the output output = stdout.decode() deployments = [] app_choices = [] lines = output.strip().split('\n') for line in lines: line = line.strip() if not line or 'โ”€' in line or line.startswith('app_id') or line.startswith('App') or line.startswith('Created') or 'App' in line or 'Status' in line or 'URL' in line or 'GPU' in line or 'Cost' in line: continue # Parse app line - format: app_id state created_at url parts = line.split() if len(parts) >= 2: app_id = parts[0] state = parts[1] if len(parts) > 1 else "unknown" created = parts[2] if len(parts) > 2 else "unknown" url = "N/A" # Try to extract URL if present if "modal.run" in line: url_parts = [p for p in parts if "modal.run" in p] if url_parts: url = url_parts[0] deployments.append([ app_id, state, url, created, "CPU", # Default GPU type "$0.30/hr" # Estimated cost ]) app_choices.append(app_id) if not deployments: deployments = [["No deployments found", "N/A", "", "", "", ""]] return gr.update(value=deployments), gr.update(choices=app_choices, value=app_choices[0] if app_choices else "") except Exception as e: logger.error(f"Error refreshing deployments: {e}") return gr.update(value=[["Error", str(e), "", "", "", ""]]), gr.update(choices=[]) def refresh_deployments_sync(): """Synchronous wrapper for refresh_deployments.""" import asyncio try: return asyncio.run(refresh_deployments()) except Exception as e: logger.error(f"Error in refresh_deployments_sync: {e}") return gr.update(value=[["Error", str(e), "", "", "", ""]]), gr.update(choices=[]) async def stop_deployment(app_id: str): """Stop a Modal deployment.""" try: if not app_id or app_id.strip() == "": return "โŒ Please select an app to stop", gr.update() import subprocess import asyncio # Run modal app stop command process = await asyncio.create_subprocess_exec( "modal", "app", "stop", app_id, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await process.communicate() if process.returncode == 0: # Refresh deployments after stopping new_data, new_choices = await refresh_deployments() return f"โœ… Successfully stopped {app_id}", new_data else: error_msg = stderr.decode() if stderr else "Unknown error" return f"โŒ Failed to stop {app_id}: {error_msg}", gr.update() except Exception as e: logger.error(f"Error stopping deployment: {e}") return f"โŒ Error stopping {app_id}: {str(e)}", gr.update() def stop_deployment_sync(app_id: str): """Synchronous wrapper for stop_deployment.""" import asyncio try: return asyncio.run(stop_deployment(app_id)) except Exception as e: logger.error(f"Error in stop_deployment_sync: {e}") return f"โŒ Error: {str(e)}", gr.update() async def get_app_logs(app_id: str): """Get logs for a Modal deployment.""" try: if not app_id or app_id.strip() == "": return "โŒ Please select an app to view logs", "" import subprocess import asyncio # Run modal app logs command process = await asyncio.create_subprocess_exec( "modal", "app", "logs", app_id, "--lines", "100", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await process.communicate() if process.returncode == 0: logs = stdout.decode() if logs.strip(): return f"โœ… Logs for {app_id}", logs else: return f"โœ… No logs found for {app_id}", "No logs available yet." else: error_msg = stderr.decode() if stderr else "Unknown error" return f"โŒ Failed to get logs for {app_id}: {error_msg}", "" except Exception as e: logger.error(f"Error getting logs: {e}") return f"โŒ Error getting logs for {app_id}: {str(e)}", "" def get_app_logs_sync(app_id: str): """Synchronous wrapper for get_app_logs.""" import asyncio try: return asyncio.run(get_app_logs(app_id)) except Exception as e: logger.error(f"Error in get_app_logs_sync: {e}") return f"โŒ Error: {str(e)}", "" # Event handlers def show_gpu_options(enable_gpu): return gr.update(visible=enable_gpu), gr.update(visible=enable_gpu) def show_remote_functions_config(enable_remote): return gr.update(visible=enable_remote) def start_link_authentication(): """Start REAL Modal token flow authentication.""" try: import aiohttp.web async def create_token_flow(): """Create real Modal token flow - exactly like debug dashboard.""" # Token flows should work WITHOUT existing authentication try: client = await _Client.from_env() except Exception: # If no existing auth, create unauthenticated client for token flow # This is what Modal CLI does for `modal token new` import os # Temporarily clear tokens to create unauthenticated client old_token_id = os.environ.pop("MODAL_TOKEN_ID", None) old_token_secret = os.environ.pop("MODAL_TOKEN_SECRET", None) try: client = await _Client.from_env() except: # If that still fails, the issue is more fundamental raise Exception("Cannot create Modal client for token flow") finally: # Restore original environment if old_token_id is not None: os.environ["MODAL_TOKEN_ID"] = old_token_id if old_token_secret is not None: os.environ["MODAL_TOKEN_SECRET"] = old_token_secret stub = client.stub # Run temporary http server async def slash(request): headers = {"Access-Control-Allow-Origin": "*"} return aiohttp.web.Response(text="token_flow_id", headers=headers) app = aiohttp.web.Application() app.add_routes([aiohttp.web.get("/", slash)]) async with run_temporary_http_server(app) as url: req = api_pb2.TokenFlowCreateRequest( utm_source="modal-for-noobs", localhost_port=int(url.split(":")[-1]), ) resp = await stub.TokenFlowCreate(req) # Construct tokens immediately - EXACTLY like debug dashboard token_id = f"ak-{resp.token_flow_id[3:]}" # Remove 'tf-', add 'ak-' token_secret = f"as-{resp.wait_secret}" # Add 'as-' prefix return { "token_flow_id": resp.token_flow_id, "web_url": resp.web_url, "code": resp.code, "wait_secret": resp.wait_secret, "token_id": token_id, "token_secret": token_secret } # Use global event loop like debug dashboard loop = get_modal_loop() result = asyncio.run_coroutine_threadsafe( create_token_flow(), loop ).result() # Open real Modal auth URL for user to authorize webbrowser.open(result["web_url"]) # Store flow info for polling (NOT the final tokens yet!) self.token_flow_id = result.get("token_flow_id") self.wait_secret = result.get("wait_secret") self.auth_url = result.get("web_url") self.auth_code = result.get("code") self.client_stub = None # Will store for polling # Show the 3 links like debug dashboard return ( self._get_auth_status_html(False, "pending"), gr.update(visible=False), # link_auth_btn result["token_flow_id"], # flow_id_display result["web_url"], # auth_url_display result["code"], # auth_code_display gr.update(visible=True), # link_progress (show waiting) gr.update(visible=False), # link_success gr.update(value=""), # link_workspace_display self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) except Exception as e: logger.error(f"Failed to start link auth: {e}") return ( self._get_auth_status_html(False, "error", str(e)), gr.update(visible=True), # link_auth_btn "", # flow_id_display "", # auth_url_display "", # auth_code_display gr.update(visible=False), # link_progress gr.update(visible=False), # link_success gr.update(value=""), # link_workspace_display self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) def check_authorization(): """Poll Modal servers to check if user completed authorization.""" try: if not hasattr(self, 'token_flow_id') or not self.token_flow_id: return ( self._get_auth_status_html(False, "error", "No active authorization flow"), gr.update(visible=True), # link_auth_btn "", # flow_id_display "", # auth_url_display "", # auth_code_display gr.update(visible=False), # link_progress gr.update(visible=False), # link_success gr.update(value=""), # link_workspace_display self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) import aiohttp.web async def poll_for_tokens(): """Poll Modal servers for completed authorization.""" # Create client for polling - EXACTLY like debug dashboard client = await _Client.from_env() stub = client.stub # Poll for completion (single attempt) req = api_pb2.TokenFlowWaitRequest( token_flow_id=self.token_flow_id, timeout=1.0, # Short timeout for immediate check wait_secret=self.wait_secret ) resp = await stub.TokenFlowWait(req, timeout=5.0) if not resp.timeout: # Authorization complete! Construct real tokens token_id = f"ak-{self.token_flow_id[3:]}" # Remove 'tf-', add 'ak-' token_secret = f"as-{self.wait_secret}" # Add 'as-' prefix return { "success": True, "token_id": token_id, "token_secret": token_secret } else: # Still waiting return {"success": False, "waiting": True} # Poll using global event loop loop = get_modal_loop() result = asyncio.run_coroutine_threadsafe( poll_for_tokens(), loop ).result() if result["success"]: # Authorization complete! Store tokens and show success self.current_auth_config = { "token_id": result["token_id"], "token_secret": result["token_secret"] } self.is_authenticated = True return ( self._get_auth_status_html(True, workspace="default"), gr.update(visible=False), # link_auth_btn "", # flow_id_display "", # auth_url_display "", # auth_code_display gr.update(visible=False), # link_progress gr.update(visible=True), # link_success gr.update(value="default"), # link_workspace_display self._get_auth_check_html(True), # gen_auth_check self._get_auth_check_html(True) # monitor_auth_check ) else: # Still waiting - show status return ( self._get_auth_status_html(False, "pending", error_msg="Still waiting for authorization... Click 'Check Authorization' again after completing in browser."), gr.update(visible=False), # link_auth_btn self.token_flow_id, # flow_id_display (keep showing) getattr(self, 'auth_url', ''), # auth_url_display (keep showing) getattr(self, 'auth_code', ''), # auth_code_display (keep showing) gr.update(visible=True), # link_progress gr.update(visible=False), # link_success gr.update(value=""), # link_workspace_display self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) except Exception as e: logger.error(f"Failed to check authorization: {e}") return ( self._get_auth_status_html(False, "error", f"Check failed: {str(e)}"), gr.update(visible=True), # link_auth_btn "", # flow_id_display "", # auth_url_display "", # auth_code_display gr.update(visible=False), # link_progress gr.update(visible=False), # link_success gr.update(value=""), # link_workspace_display self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) def authenticate_with_tokens(token_id, token_secret, workspace): """Authenticate using tokens.""" if not token_id or not token_secret: return ( "โŒ Please provide both Token ID and Token Secret!", self._get_auth_status_html(False), self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) config = ModalAuthConfig( token_id=token_id.strip(), token_secret=token_secret.strip(), workspace=workspace.strip() if workspace else None ) is_valid, errors = config.validate() if not is_valid: return ( f"โŒ Validation failed:\\n{chr(10).join(errors)}", self._get_auth_status_html(False), self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) if self.auth_manager.test_authentication(config): self.auth_manager.apply_auth_to_env(config) self.auth_manager.save_auth(config) self.is_authenticated = True self.current_auth_config = config return ( "โœ… Authentication successful! ๐ŸŽ‰", self._get_auth_status_html(True, workspace), self._get_auth_check_html(True), # gen_auth_check self._get_auth_check_html(True) # monitor_auth_check ) else: return ( "โŒ Authentication failed! Please check your tokens.", self._get_auth_status_html(False), self._get_auth_check_html(False), # gen_auth_check self._get_auth_check_html(False) # monitor_auth_check ) def generate_enhanced_deployment( file_path, code, app_name, mode, enable_gpu, gpu_type, num_gpus, enable_storage, enable_logging, enable_dashboard_monitoring, timeout, max_containers, requirements_file, python_packages, system_packages, env_vars, secrets, enable_remote_functions, remote_functions_data ): """Generate enhanced deployment with all features.""" try: # Get source code if file_path: source_code = Path(file_path).read_text() if not app_name: app_name = Path(file_path).stem elif code: source_code = code if not app_name: app_name = "my-gradio-app" else: return None, False, False, "โŒ Please provide either a file or code!" # Parse inputs python_deps = [pkg.strip() for pkg in python_packages.split(",") if pkg.strip()] if python_packages else [] system_deps = [pkg.strip() for pkg in system_packages.split(",") if pkg.strip()] if system_packages else [] # Parse environment variables env_dict = {} if env_vars: for pair in env_vars.split(","): if "=" in pair: key, value = pair.split("=", 1) env_dict[key.strip()] = value.strip() # Parse secrets secrets_list = [s.strip() for s in secrets.split(",") if s.strip()] if secrets else [] # Parse remote functions remote_functions = [] if enable_remote_functions and remote_functions_data: # Process remote functions data for func_data in remote_functions_data: remote_functions.append(func_data) # Generate deployment deployment_code = generate_from_wizard_input( app_name=app_name, deployment_mode=mode, original_code=source_code, provision_nfs=enable_storage, provision_logging=enable_logging, gpu_type=gpu_type if enable_gpu else None, python_dependencies=python_deps, system_dependencies=system_deps, requirements_file=Path(requirements_file) if requirements_file else None, environment_variables=env_dict, secrets=secrets_list, remote_functions=remote_functions ) # Create download file output_file = f"modal_{app_name}.py" with open(output_file, "w") as f: f.write(deployment_code) return ( gr.update(value=deployment_code, visible=True), gr.update(visible=True), gr.update(value=output_file, visible=True), gr.update(visible=True), "โœ… Enhanced deployment generated successfully!" ) except Exception as e: logger.error(f"Generation failed: {e}") return ( gr.update(value="", visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), f"โŒ Generation failed: {str(e)}" ) # Connect events enable_gpu.change( fn=show_gpu_options, inputs=enable_gpu, outputs=[gpu_type, num_gpus] ) enable_remote_functions.change( fn=show_remote_functions_config, inputs=enable_remote_functions, outputs=remote_functions_config ) link_auth_btn.click( fn=start_link_authentication, outputs=[auth_status_display, link_auth_btn, flow_id_display, auth_url_display, auth_code_display, link_progress, link_success, link_workspace_display, gen_auth_check, monitor_auth_check] ) link_cancel_btn.click( fn=check_authorization, outputs=[auth_status_display, link_auth_btn, flow_id_display, auth_url_display, auth_code_display, link_progress, link_success, link_workspace_display, gen_auth_check, monitor_auth_check] ) token_auth_btn.click( fn=authenticate_with_tokens, inputs=[token_id_input, token_secret_input, workspace_input], outputs=[token_result, auth_status_display, gen_auth_check, monitor_auth_check] ) generate_btn.click( fn=generate_enhanced_deployment, inputs=[ app_file_upload, code_input, app_name_input, deployment_mode, enable_gpu, gpu_type, num_gpus, enable_storage, enable_logging, enable_dashboard_monitoring, timeout_minutes, max_containers, requirements_upload, python_packages, system_packages, env_vars_input, secrets_input, enable_remote_functions, remote_functions_list ], outputs=[generation_output, output_actions, download_btn, deploy_btn, gen_auth_check] ) def deploy_to_modal(generated_code): """Deploy the generated code to Modal.""" try: if not generated_code or generated_code.strip() == "": return "โŒ No deployment code to deploy. Please generate deployment first." # Create temporary file for deployment temp_file = Path("temp_deployment.py") temp_file.write_text(generated_code) # Deploy using ModalDeployer deployer = ModalDeployer(temp_file) result = uvloop.run(deployer.deploy()) # Clean up temp file if temp_file.exists(): temp_file.unlink() if result and hasattr(result, 'success') and result.success: return f"โœ… Successfully deployed to Modal!\n\nApp URL: {result.app_url if hasattr(result, 'app_url') else 'Available in Modal console'}" else: error_msg = result.error_message if result and hasattr(result, 'error_message') else "Unknown deployment error" return f"โŒ Deployment failed: {error_msg}" except Exception as e: logger.error(f"Deployment error: {e}") return f"โŒ Deployment failed: {str(e)}" deploy_btn.click( fn=deploy_to_modal, inputs=[generation_output], outputs=[gen_auth_check] ) # Monitoring event handlers refresh_btn.click( fn=refresh_deployments_sync, outputs=[deployments_df, selected_app] ) stop_selected_btn.click( fn=lambda app_id: stop_deployment_sync(app_id), inputs=[selected_app], outputs=[gen_auth_check, deployments_df] ) def show_logs(app_id): """Show logs for selected app.""" status, logs = get_app_logs_sync(app_id) return status, gr.update(value=logs, visible=True) logs_btn.click( fn=show_logs, inputs=[selected_app], outputs=[gen_auth_check, logs_output] ) get_info_btn.click( fn=lambda app_id: f"โ„น๏ธ App info for {app_id}" if app_id else "โŒ Please select an app", inputs=[selected_app], outputs=[gen_auth_check] ) kill_app_btn.click( fn=lambda app_id: stop_deployment_sync(app_id)[0], # Just get the status message inputs=[selected_app], outputs=[gen_auth_check] ) # 3 AUTHENTICATION BUTTON HANDLERS def initialize_modal_client(): """Initialize Modal client like debug dashboard.""" try: # Use global event loop for Modal operations loop = get_modal_loop() async def init_client(): from modal.client import _Client client = await _Client.from_env() return True success = asyncio.run_coroutine_threadsafe(init_client(), loop).result() if success: return "โœ… Modal client initialized successfully" else: return "โŒ Failed to initialize Modal client" except Exception as e: return f"โŒ Error: {str(e)}" def start_auth_flow_debug(): """Start authentication flow like debug dashboard.""" try: # Use the existing start_link_authentication function result = start_link_authentication() return "โœ… Authentication flow started - check details below" except Exception as e: return f"โŒ Error starting auth flow: {str(e)}" def finish_auth_flow_debug(): """Finish authentication flow like debug dashboard.""" try: # Use the existing check_authorization function result = check_authorization() if isinstance(result, tuple) and len(result) > 0: # Check if authentication was successful if hasattr(self, 'is_authenticated') and self.is_authenticated: return "โœ… Authentication completed successfully!" else: return "๐Ÿ”„ Still waiting for authorization - complete in browser first" return "โŒ Authentication check failed" except Exception as e: return f"โŒ Error finishing auth: {str(e)}" # Connect the 3 button handlers initialize_btn.click( fn=initialize_modal_client, outputs=[gen_auth_check] ) start_auth_btn.click( fn=start_auth_flow_debug, outputs=[gen_auth_check] ) finish_auth_btn.click( fn=finish_auth_flow_debug, outputs=[gen_auth_check] ) return dashboard def _get_auth_status_html(self, is_authenticated: bool, status: str = None, workspace: str = None, error_msg: str = None) -> str: """Get authentication status HTML with all three greens.""" if is_authenticated: return f"""
โœ… Connected to Modal! {f'
Workspace: {workspace}' if workspace else ''}
""" elif status == "pending": return f"""
๐Ÿ”„ Connecting to Modal...
""" elif status == "error": return f"""
โŒ Connection Failed {f'
{error_msg}' if error_msg else ''}
""" else: return f"""
๐Ÿ” Ready to Connect
Choose your authentication method above
""" def _get_auth_check_html(self, is_authenticated: bool) -> str: """Get authentication check HTML for tabs.""" if is_authenticated: return f"""
โœ… Connected to Modal - Ready to proceed!
""" else: return f"""
โŒ Please connect to Modal first
Go to the "Connect to Modal" tab to get started
""" def _get_progress_html(self, progress: float) -> str: """Get progress bar HTML.""" return f"""

{int(progress)}% complete

""" def launch_complete_dashboard(port: int = 7860, share: bool = False): """Launch the complete Modal dashboard with all features. Args: port: Port to run the dashboard on share: Whether to create a public share link """ dashboard = CompleteModalDashboard() interface = dashboard.create_complete_interface() rprint(f"[{MODAL_GREEN}]๐Ÿš€ Launching Complete Modal Dashboard...[/{MODAL_GREEN}]") rprint(f"[{MODAL_LIGHT_GREEN}]๐Ÿ“Š Dashboard available at: http://localhost:{port}[/{MODAL_LIGHT_GREEN}]") rprint(f"[{MODAL_DARK_GREEN}]โœจ Features: Link auth, token auth, file upload, enhanced templates, monitoring![/{MODAL_DARK_GREEN}]") interface.launch( server_name="0.0.0.0", server_port=port, share=share, quiet=False, show_error=True, debug=False, strict_cors=False # Allow localhost, HuggingFace, and Modal cross-origin requests ) if __name__ == "__main__": # Direct execution - launch the dashboard immediately! print("๐Ÿš€ Launching Complete Modal Dashboard directly...") launch_complete_dashboard()