Como criar dashboards simples com PowerShell e HTML

1. Introdução ao conceito de dashboards com PowerShell e HTML

Dashboards são ferramentas essenciais para visualização de dados em tempo real ou quase real. Quando combinamos PowerShell com HTML, criamos uma solução leve, portátil e altamente personalizável para gerar relatórios visuais sem depender de ferramentas pesadas como Power BI ou Tableau.

A principal vantagem dessa abordagem é a portabilidade: um arquivo HTML pode ser aberto em qualquer navegador, compartilhado por e-mail, hospedado em servidores web ou acessado em redes locais. PowerShell, por sua vez, oferece acesso nativo a dados do sistema Windows, logs de eventos, serviços, processos e muito mais.

Cenários práticos incluem relatórios de saúde do servidor, monitoramento de serviços críticos, auditoria de eventos de segurança e dashboards de desempenho para equipes de infraestrutura.

2. Preparação do ambiente e fontes de dados

Antes de gerar o dashboard, precisamos coletar dados confiáveis. PowerShell oferece cmdlets poderosos para essa finalidade:

# Coleta de dados do sistema
$processos = Get-Process | Select-Object -First 10 Name, CPU, WorkingSet
$servicos = Get-Service | Where-Object { $_.Status -eq 'Running' }
$eventos = Get-EventLog -LogName System -Newest 20

# Estruturando dados em hashtable para facilitar o acesso
$dadosServidor = @{
    Nome = $env:COMPUTERNAME
    CPU = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average
    MemoriaTotal = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB
    MemoriaLivre = (Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB
    DiscoLivre = (Get-PSDrive C | Select-Object -ExpandProperty Free) / 1GB
}

Tratamento de erros é crucial. Use try/catch para garantir que falhas na coleta não quebrem a geração do dashboard:

try {
    $cpu = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average
} catch {
    $cpu = 0
    Write-Warning "Não foi possível obter dados de CPU"
}

3. Construção da estrutura HTML base via PowerShell

A estrutura HTML pode ser montada usando here-strings, que permitem criar blocos de texto multilinha com variáveis interpoladas:

$htmlTemplate = @"
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Dashboard - $($dadosServidor.Nome)</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; margin: 20px; background: #f5f5f5; }
        .container { max-width: 1200px; margin: auto; }
        .card { background: white; border-radius: 8px; padding: 20px; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        h1 { color: #333; border-bottom: 3px solid #0078d4; padding-bottom: 10px; }
        table { width: 100%; border-collapse: collapse; }
        th { background: #0078d4; color: white; padding: 10px; text-align: left; }
        td { padding: 8px; border-bottom: 1px solid #ddd; }
        tr:nth-child(even) { background: #f9f9f9; }
        .metric { display: inline-block; margin: 10px; padding: 15px; min-width: 200px; }
        .metric-value { font-size: 2em; font-weight: bold; color: #0078d4; }
        .progress-bar { height: 20px; background: #e0e0e0; border-radius: 10px; overflow: hidden; }
        .progress-fill { height: 100%; background: #0078d4; border-radius: 10px; transition: width 0.3s; }
        .critical { color: #d32f2f; }
        .warning { color: #f57c00; }
        .ok { color: #388e3c; }
    </style>
</head>
<body>
    <div class='container'>
        <h1>📊 Dashboard - $($dadosServidor.Nome)</h1>
"@

4. Geração de tabelas e gráficos simples

O cmdlet ConvertTo-Html é a maneira mais rápida de converter objetos em tabelas HTML, mas podemos personalizá-lo:

# Tabela personalizada de processos
$tabelaProcessos = $processos | ConvertTo-Html -Fragment -PreContent "<h2>Processos em Execução</h2>" | Out-String

# Para mais controle, montamos manualmente
$tabelaServicos = "<h2>Serviços Ativos</h2><table><tr><th>Nome</th><th>Status</th><th>Tipo</th></tr>"
foreach ($svc in $servicos) {
    $statusClass = "ok"
    if ($svc.Status -eq 'Stopped') { $statusClass = "critical" }
    $tabelaServicos += "<tr><td>$($svc.Name)</td><td class='$statusClass'>$($svc.Status)</td><td>$($svc.ServiceType)</td></tr>"
}
$tabelaServicos += "</table>"

Gráficos de barras simples podem ser criados com divs estilizadas:

# Gráfico de barras para uso de CPU por processo
$graficoCPU = "<h2>Uso de CPU por Processo (Top 5)</h2>"
$topCPU = $processos | Sort-Object CPU -Descending | Select-Object -First 5
$maxCPU = ($topCPU | Measure-Object CPU -Maximum).Maximum

foreach ($proc in $topCPU) {
    $percent = [math]::Round(($proc.CPU / $maxCPU) * 100, 0)
    $graficoCPU += @"
    <div style='margin: 5px 0;'>
        <span>$($proc.Name)</span>
        <div class='progress-bar'>
            <div class='progress-fill' style='width: $percent%'></div>
        </div>
        <span>$([math]::Round($proc.CPU, 2))%</span>
    </div>
"@
}

5. Adição de indicadores visuais e métricas

Cards de resumo com indicadores visuais tornam o dashboard mais informativo:

function New-MetricCard {
    param($Titulo, $Valor, $Unidade, $LimiteCritico, $LimiteAlerta)

    $classe = "ok"
    if ($Valor -gt $LimiteCritico) { $classe = "critical" }
    elseif ($Valor -gt $LimiteAlerta) { $classe = "warning" }

    return @"
    <div class='card metric'>
        <h3>$Titulo</h3>
        <div class='metric-value $classe'>$([math]::Round($Valor, 1)) $Unidade</div>
        <div class='progress-bar'>
            <div class='progress-fill' style='width: $([math]::Min($Valor, 100))%; background: $(if($classe -eq 'critical'){'#d32f2f'}elseif($classe -eq 'warning'){'#f57c00'}else{'#0078d4'})'></div>
        </div>
    </div>
"@
}

$cardsMetricas = @"
<div style='display: flex; flex-wrap: wrap;'>
    $(New-MetricCard -Titulo "CPU" -Valor $dadosServidor.CPU -Unidade "%" -LimiteCritico 90 -LimiteAlerta 70)
    $(New-MetricCard -Titulo "Memória Usada" -Valor (100 - ($dadosServidor.MemoriaLivre / ($dadosServidor.MemoriaTotal * 1024)) * 100) -Unidade "%" -LimiteCritico 95 -LimiteAlerta 85)
    $(New-MetricCard -Titulo "Disco C:" -Valor (100 - ($dadosServidor.DiscoLivre / 100) * 100) -Unidade "%" -LimiteCritico 95 -LimiteAlerta 85)
</div>
"@

6. Automação e atualização do dashboard

Crie uma função reutilizável para gerar o dashboard:

function New-ServerDashboard {
    param(
        [string]$OutputPath = "C:\Dashboards\dashboard.html",
        [switch]$OpenBrowser
    )

    # Coleta de dados (código das seções anteriores)
    # Montagem do HTML (código das seções anteriores)

    $htmlCompleto = $htmlTemplate + $cardsMetricas + $tabelaProcessos + $tabelaServicos + $graficoCPU + "</div></body></html>"

    $htmlCompleto | Out-File -FilePath $OutputPath -Encoding UTF8

    if ($OpenBrowser) {
        Start-Process $OutputPath
    }

    Write-Host "Dashboard gerado em: $OutputPath"
}

# Exemplo de uso
New-ServerDashboard -OpenBrowser

Para agendamento automático, use o Task Scheduler do Windows:

# Comando para criar tarefa agendada (executar como Administrador)
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\Scripts\GerarDashboard.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "08:00"
Register-ScheduledTask -TaskName "DashboardServidor" -Action $action -Trigger $trigger -User "SYSTEM"

7. Exemplo prático completo: Dashboard de monitoramento de servidor

Abaixo, um exemplo funcional completo que integra todos os conceitos:

# dashboard_completo.ps1
$dataGeracao = Get-Date -Format "dd/MM/yyyy HH:mm"

# Coleta
$cpu = [math]::Round((Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average, 1)
$memTotal = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB
$memLivre = (Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB
$memUsada = [math]::Round((1 - ($memLivre / ($memTotal * 1024))) * 100, 1)
$discoLivre = (Get-PSDrive C).Free / 1GB
$discoTotal = (Get-PSDrive C).Used / 1GB + $discoLivre
$discoUsado = [math]::Round(($discoTotal - $discoLivre) / $discoTotal * 100, 1)

$servicosCriticos = @("Spooler", "W3SVC", "MSSQLSERVER")
$statusServicos = foreach ($s in $servicosCriticos) {
    $svc = Get-Service -Name $s -ErrorAction SilentlyContinue
    [PSCustomObject]@{
        Nome = $s
        Status = if ($svc) { $svc.Status } else { "Inexistente" }
    }
}

# Montagem HTML (simplificada)
$html = @"
<!DOCTYPE html>
<html>
<head><title>Dashboard - $env:COMPUTERNAME</title>
<style>
    body { font-family: Arial; background: #f0f0f0; padding: 20px; }
    .card { background: white; border-radius: 10px; padding: 20px; margin: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
    .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; }
    .valor { font-size: 2.5em; font-weight: bold; }
    .critico { color: red; }
    .barrinha { height: 25px; background: #e0e0e0; border-radius: 12px; margin: 10px 0; }
    .barrinha div { height: 100%; border-radius: 12px; }
</style></head>
<body>
    <h1>📊 Dashboard - $env:COMPUTERNAME</h1>
    <p>Gerado em: $dataGeracao</p>
    <div class='grid'>
        <div class='card'><h3>CPU</h3><div class='valor $(if($cpu -gt 90){'critico'})'>$cpu%</div><div class='barrinha'><div style='width:$cpu%;background:$(if($cpu -gt 90){'red'}else{'#0078d4'})'></div></div></div>
        <div class='card'><h3>Memória</h3><div class='valor $(if($memUsada -gt 95){'critico'})'>$memUsada%</div><div class='barrinha'><div style='width:$memUsada%;background:$(if($memUsada -gt 95){'red'}else{'#0078d4'})'></div></div></div>
        <div class='card'><h3>Disco C:</h3><div class='valor $(if($discoUsado -gt 95){'critico'})'>$discoUsado%</div><div class='barrinha'><div style='width:$discoUsado%;background:$(if($discoUsado -gt 95){'red'}else{'#0078d4'})'></div></div></div>
    </div>
    <div class='card'>
        <h3>Serviços Críticos</h3>
        <table><tr><th>Serviço</th><th>Status</th></tr>
        $($statusServicos | ForEach-Object { "<tr><td>$($_.Nome)</td><td style='color:$(if($_.Status -eq 'Running'){'green'}else{'red'})'>$($_.Status)</td></tr>" })
        </table>
    </div>
</body></html>
"@

$html | Out-File "C:\Dashboard\servidor.html" -Encoding UTF8
Start-Process "C:\Dashboard\servidor.html"

8. Boas práticas e extensões possíveis

Para manter o código sustentável, separe a lógica em módulos:

# Estrutura de pastas recomendada
# C:\Scripts\Dashboard\
#   ├── ColetaDados.ps1
#   ├── TemplateHTML.ps1
#   ├── GerarDashboard.ps1
#   └── Agendamento.ps1

Para dashboards mais profissionais, integre frameworks CSS como Bootstrap:

# Adicione no <head> do HTML
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">

Extensões possíveis incluem integração com bancos de dados SQL via Invoke-SqlCmd, consumo de APIs REST com Invoke-RestMethod, geração de gráficos com Chart.js (adicionando JavaScript ao HTML) e envio por e-mail com Send-MailMessage.

Referências