Start-AzureLocalServer.ps1

<#
.SYNOPSIS
    Start the Azure Local Inventory web dashboard.
.DESCRIPTION
    Launches a local HTTP server on the specified port and opens a browser-ready dashboard
    for exploring Azure Local (Azure Stack HCI) inventory. The dashboard provides
    authentication flow, WAF assessment, cost analysis, and executive PDF export.
.PARAMETER Port
    TCP port for the local web server. Defaults to 8081.
.EXAMPLE
    Start-AzureLocalServer
    Starts the dashboard on the default port (8081).
.EXAMPLE
    Start-AzureLocalServer -Port 9090
    Starts the dashboard on port 9090.
#>

function Start-AzureLocalServer {
    [CmdletBinding()]
    param(
        [int]$Port = 8081
    )

    $moduleRoot = $PSScriptRoot

    Write-Host "🚀 Starting Azure Local Inventory Server..." -ForegroundColor Cyan
    Write-Host "✓ PowerShell Version: $($PSVersionTable.PSVersion)" -ForegroundColor Green

    # Check Azure connection (private helper)
    function Test-AzureConnection {
    try {
        $context = Get-AzContext
        if ($null -eq $context) {
            return $false
        }
        return $true
    }
    catch {
        return $false
    }
}

    # Server-scoped state
    $isAuthenticated = Test-AzureConnection
    $inventoryData   = @{}
    $lastUpdate      = $null
    $wafConfig       = $null

    # Load WAF Configuration
    $wafConfigPath = Join-Path $moduleRoot "waf-config.json"
    if (Test-Path $wafConfigPath) {
        try {
            $wafConfig = Get-Content $wafConfigPath -Raw | ConvertFrom-Json
            Write-Host "✓ WAF Configuration loaded (Version: $($wafConfig.version))" -ForegroundColor Green
        }
        catch {
            Write-Host "⚠️ Failed to load WAF configuration: $($_.Exception.Message)" -ForegroundColor Yellow
        }
    }
    else {
        Write-Host "⚠️ WAF configuration file not found at: $wafConfigPath" -ForegroundColor Yellow
    }

    Write-Host "🔐 Azure Authentication Status: $(if ($isAuthenticated) { 'Connected ✓' } else { 'Not Connected ✗' })" -ForegroundColor $(if ($isAuthenticated) { 'Green' } else { 'Yellow' })

    # HTTP Listener
    $listener = New-Object System.Net.HttpListener
    $listener.Prefixes.Add("http://localhost:$Port/")
    $listener.Start()

    Write-Host "🌐 Server started at http://localhost:$Port" -ForegroundColor Green
    Write-Host "📊 Access the Azure Local Inventory Dashboard in your browser" -ForegroundColor Cyan
    Write-Host "Press Ctrl+C to stop the server" -ForegroundColor Gray
    Write-Host ""

try {
    while ($listener.IsListening) {
        $context = $listener.GetContext()
        $request = $context.Request
        $response = $context.Response
        
        $path = $request.Url.AbsolutePath
        $method = $request.HttpMethod
        
        Write-Host "$(Get-Date -Format 'HH:mm:ss') $method $path" -ForegroundColor Gray
        
        # Content type and response
        $content = ""
        $contentType = "text/html; charset=utf-8"
        
        # Route handling
        switch -Regex ($path) {
            '^/$' {
                # Serve main page
                $indexPath = Join-Path $moduleRoot "index.html"
                if (Test-Path $indexPath) {
                    $content = Get-Content $indexPath -Raw
                } else {
                    $content = "<html><body><h1>Azure Local Inventory Server</h1><p>index.html not found</p></body></html>"
                }
            }
            
            '^/styles\.css$' {
                $cssPath = Join-Path $moduleRoot "styles.css"
                if (Test-Path $cssPath) {
                    $content = Get-Content $cssPath -Raw
                    $contentType = "text/css"
                }
            }
            
            '^/app\.js$' {
                $jsPath = Join-Path $moduleRoot "app.js"
                if (Test-Path $jsPath) {
                    $content = Get-Content $jsPath -Raw
                    $contentType = "application/javascript"
                }
            }
            
            '^/api/auth/status$' {
                $isAuthenticated = Test-AzureConnection
                $authStatus = @{
                    authenticated = $isAuthenticated
                    context = if ($isAuthenticated) {
                        $ctx = Get-AzContext
                        @{
                            account = $ctx.Account.Id
                            subscription = $ctx.Subscription.Name
                            tenant = $ctx.Tenant.Id
                        }
                    } else { $null }
                }
                $content = $authStatus | ConvertTo-Json
                $contentType = "application/json"
            }
            
            '^/api/auth/login$' {
                try {
                    Connect-AzAccount -UseDeviceAuthentication | Out-Null
                    $isAuthenticated = $true
                    $content = @{ success = $true; message = "Authentication successful" } | ConvertTo-Json
                } catch {
                    $content = @{ success = $false; message = $_.Exception.Message } | ConvertTo-Json
                }
                $contentType = "application/json"
            }
            
            '^/api/waf/config$' {
                # Serve WAF configuration
                try {
                    if ($null -ne $wafConfig) {
                        Write-Host " 📋 Serving WAF configuration" -ForegroundColor Cyan
                        $content = $wafConfig | ConvertTo-Json -Depth 20 -Compress:$false
                        Write-Host " ✅ WAF config sent ($(($content.Length / 1KB).ToString('N2')) KB)" -ForegroundColor Green
                    } else {
                        Write-Host " ⚠️ WAF configuration not available" -ForegroundColor Yellow
                        $response.StatusCode = 503
                        $content = @{ error = "WAF configuration not loaded" } | ConvertTo-Json
                    }
                } catch {
                    Write-Host " ❌ Error serving WAF config: $($_.Exception.Message)" -ForegroundColor Red
                    $response.StatusCode = 500
                    $content = @{ error = $_.Exception.Message } | ConvertTo-Json
                }
                $contentType = "application/json"
            }
            
            '^/api/inventory/progress$' {
                # Return current inventory collection progress
                try {
                    $progress = Get-CollectionProgress
                    $content = $progress | ConvertTo-Json
                } catch {
                    $response.StatusCode = 500
                    $content = @{ 
                        message = "Initializing..."
                        percent = 0
                        lastUpdate = (Get-Date).ToString('o')
                        error = $_.Exception.Message 
                    } | ConvertTo-Json
                }
                $contentType = "application/json"
            }
            
            '^/api/inventory/data$' {
                if ($isAuthenticated) {
                    try {
                        Write-Host " 📊 Collecting Azure Local inventory..." -ForegroundColor Cyan
                        # Reset progress before starting
                        Update-CollectionProgress "Starting inventory collection..." 0
                        $inventoryData = Get-AzureLocalInventory
                        $lastUpdate    = Get-Date
                        $content = $inventoryData | ConvertTo-Json -Depth 10
                    } catch {
                        $content = @{ error = $_.Exception.Message } | ConvertTo-Json
                    }
                } else {
                    $response.StatusCode = 401
                    $content = @{ error = "Not authenticated" } | ConvertTo-Json
                }
                $contentType = "application/json"
            }
            
            '^/api/inventory/refresh$' {
                if ($isAuthenticated) {
                    try {
                        Write-Host " 🔄 Refreshing inventory..." -ForegroundColor Cyan
                        # Reset progress before starting
                        Update-CollectionProgress "Starting inventory refresh..." 0
                        $inventoryData = Get-AzureLocalInventory
                        $lastUpdate    = Get-Date
                        $content = @{ 
                            success    = $true
                            lastUpdate = $lastUpdate.ToString('o')
                        } | ConvertTo-Json
                    } catch {
                        $content = @{ success = $false; error = $_.Exception.Message } | ConvertTo-Json
                    }
                } else {
                    $response.StatusCode = 401
                    $content = @{ success = $false; error = "Not authenticated" } | ConvertTo-Json
                }
                $contentType = "application/json"
            }
            
            default {
                $response.StatusCode = 404
                $content = "<html><body><h1>404 - Not Found</h1></body></html>"
            }
        }
        
        # Send response
        $buffer = [System.Text.Encoding]::UTF8.GetBytes($content)
        $response.ContentLength64 = $buffer.Length
        $response.ContentType = $contentType
        $response.Headers.Add("Access-Control-Allow-Origin", "*")
        $response.OutputStream.Write($buffer, 0, $buffer.Length)
        $response.Close()
    }
}
    finally {
        Write-Host "`n🛑 Stopping server..." -ForegroundColor Yellow
        $listener.Stop()
        $listener.Close()
        Write-Host "✓ Server stopped" -ForegroundColor Green
    }
}