/** * DeploymentModal - Shows Azure deployment instructions and Docker templates * Features: Docker setup files, Azure Container Apps deployment guide */ import { useState, useEffect, useRef } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogClose, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Rocket, Container, Cloud, Copy, CheckCircle2, ExternalLink, Loader2, AlertCircle, } from "lucide-react"; import { useDevUIStore } from "@/stores"; import { apiClient } from "@/services/api"; import type { AgentInfo, WorkflowInfo } from "@/types"; interface DeploymentModalProps { open: boolean; onClose: () => void; agentName?: string; entity?: AgentInfo | WorkflowInfo; } type Tab = "docker" | "azure"; export function DeploymentModal({ open, onClose, agentName = "Agent", entity, }: DeploymentModalProps) { // Get the Azure deployment feature flag from store const azureDeploymentEnabled = useDevUIStore((state) => state.azureDeploymentEnabled); // Check if deployment is truly supported (both feature flag and backend support) const deploymentSupported = azureDeploymentEnabled && (entity?.deployment_supported ?? false); // Context-aware tab ordering: Azure first if deployable, Docker first otherwise const [activeTab, setActiveTab] = useState( deploymentSupported ? "azure" : "docker" ); const [copiedTemplate, setCopiedTemplate] = useState(null); const timeoutRef = useRef(null); const logsContainerRef = useRef(null); // Deployment state from Zustand const isDeploying = useDevUIStore((state) => state.isDeploying); const deploymentLogs = useDevUIStore((state) => state.deploymentLogs); const lastDeployment = useDevUIStore((state) => state.lastDeployment); const startDeployment = useDevUIStore((state) => state.startDeployment); const addDeploymentLog = useDevUIStore((state) => state.addDeploymentLog); const setDeploymentResult = useDevUIStore((state) => state.setDeploymentResult); const stopDeployment = useDevUIStore((state) => state.stopDeployment); const clearDeploymentState = useDevUIStore((state) => state.clearDeploymentState); // Generate Azure-compliant default app name from entity name const generateDefaultAppName = (entityName: string) => { // Convert to lowercase, replace spaces and underscores with hyphens // Remove any non-alphanumeric characters except hyphens // Ensure it starts with a letter and is under 32 chars const cleaned = entityName .toLowerCase() .replace(/[_\s]+/g, '-') // Replace underscores and spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove any other special characters .replace(/--+/g, '-') // Replace multiple hyphens with single .replace(/^[^a-z]+/, '') // Remove non-letter prefix .replace(/-$/, ''); // Remove trailing hyphen // Ensure it starts with a letter, add 'app-' prefix if needed const withPrefix = cleaned.match(/^[a-z]/) ? cleaned : `app-${cleaned}`; // Truncate to 31 chars max (32 limit) return withPrefix.substring(0, 31); }; // Form state for deployment with smart defaults const defaultAppName = entity ? generateDefaultAppName(entity.id) : ""; const [resourceGroup, setResourceGroup] = useState("my-test-rg"); const [appName, setAppName] = useState(defaultAppName); const [region, setRegion] = useState("eastus"); const [appNameError, setAppNameError] = useState(null); // Update app name when entity changes or modal opens useEffect(() => { if (entity) { const newDefaultName = generateDefaultAppName(entity.id); setAppName(newDefaultName); // Validate the default name const error = validateAppName(newDefaultName); setAppNameError(error); } }, [entity?.id]); // Only re-run when entity ID changes // Auto-scroll deployment logs to bottom when new logs are added useEffect(() => { if (logsContainerRef.current && deploymentLogs.length > 0) { logsContainerRef.current.scrollTop = logsContainerRef.current.scrollHeight; } }, [deploymentLogs]); // Validate Azure Container App name const validateAppName = (name: string): string | null => { if (!name) return null; // Don't show error for empty field // Check length if (name.length >= 32) { return "App name must be less than 32 characters"; } // Check for valid characters (lowercase alphanumeric and hyphens only) if (!/^[a-z0-9-]+$/.test(name)) { return "App name must contain only lowercase letters, numbers, and hyphens (no underscores or uppercase)"; } // Must start with a letter if (!/^[a-z]/.test(name)) { return "App name must start with a lowercase letter"; } // Must end with alphanumeric if (!/[a-z0-9]$/.test(name)) { return "App name must end with a letter or number"; } // Cannot have double hyphens if (name.includes("--")) { return "App name cannot contain consecutive hyphens (--)"; } return null; }; // Cleanup timeout on unmount useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); const handleDeploy = async () => { if (!entity?.id || !resourceGroup || !appName) return; // Trim whitespace from inputs const trimmedResourceGroup = resourceGroup.trim(); const trimmedAppName = appName.trim(); // Validate trimmed app name before deployment const nameError = validateAppName(trimmedAppName); if (nameError) { setAppNameError(nameError); return; } try { startDeployment(); for await (const event of apiClient.streamDeployment({ entity_id: entity.id, resource_group: trimmedResourceGroup, app_name: trimmedAppName, region, ui_mode: "user", })) { addDeploymentLog(event.message); if (event.type === "deploy.completed" && event.url && event.auth_token) { setDeploymentResult({ url: event.url, authToken: event.auth_token, }); } else if (event.type === "deploy.failed") { // Stop deploying but keep logs visible stopDeployment(); } } } catch (error) { addDeploymentLog(`Error: ${error instanceof Error ? error.message : "Deployment failed"}`); stopDeployment(); } }; const handleCopy = async (template: string, templateName: string) => { try { await navigator.clipboard.writeText(template); setCopiedTemplate(templateName); // Clear any existing timeout if (timeoutRef.current) { clearTimeout(timeoutRef.current); } // Set new timeout with cleanup timeoutRef.current = setTimeout(() => { setCopiedTemplate(null); timeoutRef.current = null; }, 2000); } catch { // Reset state on error - clipboard write failed setCopiedTemplate(null); } }; const dockerfileTemplate = `# Dockerfile for ${agentName} FROM python:3.11-slim WORKDIR /app # Install dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy agent/workflow directories COPY . . # Expose DevUI default port EXPOSE 8080 # Run DevUI server CMD ["devui", ".", "--port", "8080", "--host", "0.0.0.0"] `; const dockerComposeTemplate = `# docker-compose.yml version: '3.8' services: ${agentName.toLowerCase().replace(/\s+/g, "-")}: build: . environment: # OpenAI - OPENAI_API_KEY=\${OPENAI_API_KEY} - OPENAI_CHAT_MODEL=\${OPENAI_CHAT_MODEL:-gpt-4o-mini} # Or Azure OpenAI - AZURE_OPENAI_API_KEY=\${AZURE_OPENAI_API_KEY} - AZURE_OPENAI_ENDPOINT=\${AZURE_OPENAI_ENDPOINT} - AZURE_OPENAI_DEPLOYMENT_NAME=\${AZURE_OPENAI_DEPLOYMENT_NAME} # Optional: Enable instrumentation - ENABLE_INSTRUMENTATION=\${ENABLE_INSTRUMENTATION:-false} ports: - "8080:8080" restart: unless-stopped `; const requirementsTemplate = `# requirements.txt agent-framework-devui>=0.1.0 agent-framework>=0.1.0 # Chat clients (install what you need) openai>=1.0.0 # azure-openai # anthropic `; return ( Deploy {agentName}

Get started with containerizing your agent for deployment.

{/* Tabs */}
{deploymentSupported && ( )}
{/* Tab Content */}
{activeTab === "docker" && (

Containerize with Docker

Package your agent as a Docker container for consistent deployment anywhere.

{/* Dockerfile */}
Dockerfile
                      {dockerfileTemplate}
                    
{/* docker-compose.yml */}
docker-compose.yml
                      {dockerComposeTemplate}
                    
{/* requirements.txt */}
requirements.txt
                      {requirementsTemplate}
                    
{/* Quick Start */}

Quick Start

  1. Save the files above to your project directory
  2. Build:{" "} docker build -t {agentName.toLowerCase()}-agent .
  3. Run:{" "} docker-compose up
  4. Your agent is now running in a container!
{/* Production Warnings */}

⚠️ Production Considerations

  • In-memory state: Conversations are lost when container restarts
  • No authentication: Add reverse proxy (nginx, Caddy) with auth for production
  • Security: Use Azure Key Vault for secrets management
  • Scaling: Single instance only due to in-memory conversation store
{/* Deployment Checklist */}

Pre-Deployment Checklist

Set environment variables (API keys, secrets)
Test agent locally in container
Configure logging and monitoring
Set up error handling and retries
)} {activeTab === "azure" && (

Deploy to Azure Container Apps

{deploymentSupported ? "One-click deployment to Azure with automatic containerization and authentication." : "Azure Container Apps provides serverless containers with auto-scaling and integrated monitoring."}

{/* Prerequisites Notice */}

Prerequisites for Azure Deployment

  • Azure CLI installed and authenticated (az login)
  • Docker installed and running
  • Azure subscription with the following providers registered:
    • Microsoft.App (Container Apps)
    • Microsoft.ContainerRegistry (ACR)
    • Microsoft.OperationalInsights (Logging)
How to register providers?

Run these commands once per subscription:

az provider register -n Microsoft.App --wait
az provider register -n Microsoft.ContainerRegistry --wait
az provider register -n Microsoft.OperationalInsights --wait
{/* Functional Deployment Form (only if supported) */} {deploymentSupported && entity && !lastDeployment && (
{!isDeploying ? ( <>
setResourceGroup(e.target.value)} />
{ const newName = e.target.value; setAppName(newName); // Validate on change to provide immediate feedback // Trim for validation to match what will be sent const error = validateAppName(newName.trim()); setAppNameError(error); }} /> {appNameError && (

{appNameError}

)}
) : (
Deploying...
{deploymentLogs.map((log, i) => (
{log}
))}
)} {/* Show logs after deployment stops (success or failure) */} {!isDeploying && deploymentLogs.length > 0 && !lastDeployment && (
Deployment Failed
{deploymentLogs.map((log, i) => (
{log}
))}
)}
)} {/* Success Screen */} {lastDeployment && (

Deployment Successful!

{lastDeployment.url}
{lastDeployment.authToken}
)} {/* Deployment Not Supported Warning */} {!deploymentSupported && entity?.deployment_reason && (
Deployment not available: {entity.deployment_reason}
)} {/* CLI Instructions (only show when deployment not supported) */} {!deploymentSupported && ( <> {/* Prerequisites */}

Prerequisites

  • Azure subscription
  • Azure CLI installed ( az --version )
  • Docker installed and running
  • Logged in to Azure:{" "} az login
{/* Step-by-step */}

Deployment Steps

{/* Step 1 */}
1
Create Azure Container Registry
                              {`# Create resource group
az group create --name myResourceGroup --location eastus

# Create container registry
az acr create --resource-group myResourceGroup \\
  --name myregistry --sku Basic`}
                            
{/* Step 2 */}
2
Build and Push Docker Image
                              {`# Build and push in one command
az acr build --registry myregistry \\
  --image ${agentName.toLowerCase()}-agent:latest .`}
                            
{/* Step 3 */}
3
Create Container Apps Environment
                              {`az containerapp env create --name myEnvironment \\
  --resource-group myResourceGroup \\
  --location eastus`}
                            
{/* Step 4 */}
4
Deploy Container App
                              {`az containerapp create --name ${agentName.toLowerCase()}-app \\
  --resource-group myResourceGroup \\
  --environment myEnvironment \\
  --image myregistry.azurecr.io/${agentName.toLowerCase()}-agent:latest \\
  --target-port 8080 \\
  --ingress 'external' \\
  --registry-server myregistry.azurecr.io \\
  --env-vars OPENAI_API_KEY=secretref:openai-key OPENAI_CHAT_MODEL=gpt-4o-mini`}
                            
{/* Step 5 */}
5
Get Application URL
                              {`az containerapp show --name ${agentName.toLowerCase()}-app \\
  --resource-group myResourceGroup \\
  --query properties.configuration.ingress.fqdn`}
                            
{/* Learn More */}

Learn More

Explore Azure Container Apps documentation for advanced features like scaling, monitoring, and CI/CD integration.

)}
)}
); }