"""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()