public/Start-UnraidMonitor.ps1

function Start-UnraidMonitor {
    <#
    .SYNOPSIS
        Interactive dashboard showing server status (like 'top') but docker focused.

    .PARAMETER RefreshInterval
        Refresh rate in seconds (default 2).

    .PARAMETER Session
        Unraid session (defaults to current session).

    .PARAMETER LogPath
        Optional path to log metrics data for troubleshooting.

    .EXAMPLE
        Start-UnraidMonitor
    #>

    [CmdletBinding()]
    [OutputType('void')]
    param(
        [Parameter(Position = 0)]
        [int]$RefreshInterval = 2,

        [Parameter()]
        [UnraidSession]$Session = $script:DefaultUnraidSession,

        [Parameter()]
        [string]$LogPath
    )

    process {
        try {
            $diskQuery = @"
            query MonitorDisks {
                array {
                    parityCheckStatus { status progress speed }
                    parities { name device status temp numErrors size isSpinning }
                    disks { name device status temp numErrors size fsSize fsUsed isSpinning }
                    caches { name device status temp numErrors size fsSize fsUsed isSpinning }
                }
            }
"@


            while ($true) {
                # Quit if 'Q' key is pressed
                if ($Host.UI.RawUI.KeyAvailable) {
                    $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
                    if ($key.Character -eq 'q' -or $key.Character -eq 'Q') {
                        break
                    }
                }

                $metrics    = Get-UnraidMetrics
                $diskResult = Invoke-UnraidQuery -Query $diskQuery -Session $Session 
                $arrayData  = $diskResult.array

                # Quick file logging if requested
                if ($LogPath) {
                    $logEntry = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] CPU: $($metrics.CpuTotal)% | Mem: $($metrics.MemPercent)"
                    Add-Content -Path $LogPath -Value $logEntry
                }

                Clear-Host
                
                Write-Host "top - $(Get-Date -Format 'HH:mm:ss') up (Unraid) [Press 'Q' to quit]" -ForegroundColor White
                
                Write-Host "Cpu(s): " -NoNewline -ForegroundColor Cyan
                Write-Host "$($metrics.CpuTotal)% Total Load"

                $memTotal = [UnraidSizeFix]::FormatBytes($metrics.MemTotal)
                $memFree  = [UnraidSizeFix]::FormatBytes($metrics.MemFree)
                $memUsed  = [UnraidSizeFix]::FormatBytes($metrics.MemUsed)
                
                Write-Host "Mem: " -NoNewline -ForegroundColor Cyan
                Write-Host "$memTotal total, $memFree free, $memUsed used"

                if ($arrayData.parityCheckStatus.status -notin @("IDLE", $null)) {
                    $status   = $arrayData.parityCheckStatus.status
                    $progress = $arrayData.parityCheckStatus.progress
                    $speed    = $arrayData.parityCheckStatus.speed
                    
                    Write-Host "`nPARITY: " -NoNewline -ForegroundColor Magenta
                    Write-Host "$status ($progress% @ $speed)" -ForegroundColor White
                }

                $header = "DEVICE".PadRight(9) + "NAME".PadRight(15) + "TEMP".PadRight(7) + "SPIN".PadRight(7) + "STATUS".PadRight(15) + "ERRORS".PadRight(10) + "USAGE"
                Write-Host "`n$header" -BackgroundColor Gray -ForegroundColor Black

                $renderRow = {
                    param($diskItem, $isParity)
                    
                    $deviceName = if ($diskItem.device) { $diskItem.device } else { "-" }
                    $diskName   = if ($diskItem.name) { $diskItem.name } else { "Unknown" }
                    
                    if ($diskItem.isSpinning) {
                        $spinDisplay = "UP"
                        $tempDisplay = "$($diskItem.temp)C"
                    }
                    else {
                        $spinDisplay = "DWN"
                        $tempDisplay = "*"
                    }

                    $statusColor = if ($diskItem.status -ne "DISK_OK") { "Red" } 
                                   elseif (! $diskItem.isSpinning) { "DarkGray" } 
                                   else { "Green" }

                    $usageDisplay = "-"
                    if (! $isParity) {
                        $capacity = if ($diskItem.fsSize) { $diskItem.fsSize } else { $diskItem.size }
                        if ($capacity -gt 0) {
                            $percentUsed  = [math]::Round(($diskItem.fsUsed / $capacity) * 100, 0)
                            $usageDisplay = "$percentUsed%"
                        }
                    }

                    # Padding field a bit to keep alignment
                    $line = $deviceName.PadRight(9) + 
                            $diskName.PadRight(15) + 
                            $tempDisplay.PadRight(7) + 
                            $spinDisplay.PadRight(7) + 
                            $diskItem.status.PadRight(15) + 
                            "$($diskItem.numErrors)".PadRight(10) + 
                            $usageDisplay
                    
                    Write-Host $line -ForegroundColor $statusColor
                }

                if ($arrayData.parities) { $arrayData.parities | ForEach-Object { & $renderRow $_ $true } }
                if ($arrayData.disks)    { $arrayData.disks    | ForEach-Object { & $renderRow $_ $false } }
                if ($arrayData.caches)   { $arrayData.caches   | ForEach-Object { & $renderRow $_ $false } }

                Start-Sleep -Seconds $RefreshInterval
            }
        }
        catch {
            # Just exit loop on error
        }
        finally { Write-Host "`nExiting Monitor..." -ForegroundColor Yellow  }
    }
}