Ir para o conteúdo

📊 Explicação Detalhada - Maestro Dashboard: Gerando os gráficos e exibindo-os na página web com o Streamlit:

Após realizarmos a autenticação na API e construir os headers, podemos seguir com a estruturação dos gráficos e montagem da página web exibindo os gráficos. A seguir, veja como manipularemos o endpoint de listagem de erros e alertas, as manipulações nas respostas das chamadas aos endpoints e estruturação da página web com os gráficos formatados.

📌 Resumo do Fluxo Completo

┌─────────────────────────────────────┐
│  Usuário preenche credenciais       │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Clica em "Conectar e Atualizar"    │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Função login() autentica usuário   │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Obtém Token + Organização          │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  main() busca dados via fetch_data()│
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Renderiza gráficos e tabelas       │
└─────────────────────────────────────┘

1️. Configuração Inicial da Página

Abaixo, veja o snippet de código para inicializar a página com o layout exibindo os gráficos:

st.set_page_config(page_title="Maestro Dashboard", page_icon="📊", layout="wide")

O código acima define as configurações básicas da página Streamlit:

  • page_title: Título que aparece na aba do navegador
  • page_icon: Ícone exibido na aba
  • layout: Layout em modo "wide" para aproveitar toda a largura disponível

2️. Função de Login

def login(server, login, key):
    """Realiza o login no Maestro e retorna o token e organização."""
    login_url = f"{server}/api/v2/workspace/login"
    body = {"login": login, "key": key}
    try:
        response = requests.post(url=login_url, json=body, timeout=10)
        response.raise_for_status()
        content = response.json()
        return content['accessToken'], content['organizationLabel']
    except Exception as e:
        st.error(f"Erro na autenticação: {e}")
        return None, None

O que faz:

  • Constrói a URL do endpoint de login do Maestro
  • Envia uma requisição POST com as credenciais (login e chave de API)
  • Se bem-sucedido, extrai e retorna o accessToken e organizationLabel
  • Se houver erro, exibe uma mensagem de erro e retorna None, None

Fluxo:

Credenciais do usuário → POST para API → Recebe Token + Organização

3️. Função de Busca de Dados

def fetch_data(server, headers, endpoint):
    """Busca dados de um endpoint específico do Maestro."""
    url = f"{server}/api/v2/{endpoint}"
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
        return response.json().get('content', [])
    except Exception as e:
        st.warning(f"Erro ao buscar {endpoint}: {e}")
        return []

O que faz:

  • Monta a URL do endpoint solicitado
  • Faz uma requisição GET passando o token de autenticação nos headers
  • Extrai o conteúdo da resposta JSON
  • Em caso de erro, exibe um aviso e retorna uma lista vazia

Exemplos de uso:

fetch_data(server_input, headers, "alerts")  # Busca alertas
fetch_data(server_input, headers, "error")   # Busca erros

4️. Busca de Dados no Dashboard

# Buscando dados
alerts_content = fetch_data(server_input, headers, "alerts")
errors_content = fetch_data(server_input, headers, "error")

Aqui o código realiza duas requisições simultâneas:

  • alerts_content: Lista com todos os alertas
  • errors_content: Lista com todos os erros

5️. Exibição de Métricas

# Contagem de registros de erros e alertas
cont_erros, cont_alertas = st.columns(2)
cont_erros.metric("Total de Alertas", len(alerts_content))
cont_alertas.metric("Total de Erros", len(errors_content))

O que faz:

  • Cria 2 colunas lado a lado
  • Exibe o número total de alertas e erros usando cards de métrica
  • Usa len() para contar o total de registros

Resultado visual:

┌──────────────────┬──────────────────┐
│  Total Alertas      Total Erros    │
│       42                18         │
└──────────────────┴──────────────────┘

6️. Processamento de Alertas

# --- Processamento de Alertas ---
alert_counts = Counter(item['botId'] for item in alerts_content)
bots_alerts = list(alert_counts.keys())
alert_values = list(alert_counts.values())

O que faz:

  • Usa Counter para contar quantos alertas cada bot gerou
  • Extrai os IDs dos bots em uma lista
  • Extrai as contagens em outra lista

Exemplo:

# Se alert_counts = {'bot_001': 5, 'bot_002': 3, 'bot_003': 7}
bots_alerts = ['bot_001', 'bot_002', 'bot_003']
alert_values = [5, 3, 7]

7️. Processamento de Erros

# --- Processamento de Erros ---
error_counts = Counter(item['botId'] for item in errors_content)
bots_errors = list(error_counts.keys())
error_values = list(error_counts.values())

error_categories = Counter(item.get('type', 'N/A') for item in errors_content)

O que faz:

  • Conta erros por bot (similar aos alertas)
  • Também conta quantos erros de cada tipo (categoria) ocorreram
  • Usa .get('type', 'N/A') para evitar erros se a chave não existir

8️. Gráfico de Barras - Alertas

with conteiner_alertas:
    st.subheader("⚠️ Alertas por Automação")
    fig_alerts = go.Figure(data=[go.Bar(x=bots_alerts, y=alert_values, marker_color='mediumturquoise')])
    fig_alerts.update_layout(template='plotly_dark', margin=dict(l=20, r=20, t=40, b=20))
    st.plotly_chart(fig_alerts, use_container_width=True)

O que faz:

  • Cria um gráfico de barras com Plotly
  • Eixo X: IDs dos bots
  • Eixo Y: Quantidade de alertas
  • Cor: Turquesa
  • Tema: Dark
  • use_container_width=True: Ocupa toda a largura disponível

Resultado: Gráfico visual mostrando qual bot tem mais alertas

9️. Gráfico de Pizza - Categorias de Erros

with conteiner_categorias:
    st.subheader("📉 Categorias de Erros")
    if error_categories:
        pie_chart = go.Figure(data=[go.Pie(
            labels=list(error_categories.keys()), 
            values=list(error_categories.values()),
            hole=.3
        )])
        pie_chart.update_traces(hoverinfo='label+percent', textinfo='value', marker=dict(line=dict(color='#000000', width=1)))
        st.plotly_chart(pie_chart, use_container_width=True)
    else:
        st.info("Sem dados de categorias para exibir.")

O que faz:

  • Verifica se há dados de erros
  • Cria um gráfico de pizza (donut) com hole=.3
  • Labels: tipos de erro
  • Values: quantidade de cada tipo
  • Exibe percentuais e contagens no hover

10 Tabela de Últimos Erros

with conteiners_lista:
    st.subheader("📋 Últimos Erros Detectados")
    if errors_content:
        # Mostra os últimos 5 erros em formato de tabela
        st.table([{"Bot ID": e['botId'], "Tipo": e['type'], "Data": e.get('dateCreation', 'N/A')} for e in errors_content[:5]])
    else:
        st.write("Nenhum erro recente.")

O que faz:

  • Extrai os 5 primeiros erros ([:5])
  • Cria uma tabela com 3 colunas: Bot ID, Tipo e Data
  • Usa e.get('dateCreation', 'N/A') para retornar "N/A" se a data não existir

Resultado:

┌──────────┬─────────────┬────────────────────────┐
│ Bot ID   │ Tipo        │ Data                   │
├──────────┼─────────────┼────────────────────────┤
│ bot_001  │ Connection  │ 2024-01-14 10:30:00    │
│ bot_002  │ Timeout     │ 2024-01-14 09:15:00    │
│ bot_003  │ Auth Error  │ 2024-01-14 08:45:00    │
└──────────┴─────────────┴────────────────────────┘

st.sidebar.title("🔑 Configurações")
server_input = st.sidebar.text_input("Server URL", value="https://developers.botcity.dev")
login_input = st.sidebar.text_input("Login")
key_input = st.sidebar.text_input("API Key", type="password")
btn_conectar = st.sidebar.button("Conectar e Atualizar")

O que faz:

  • Cria uma barra lateral (sidebar) com título
  • Campo de entrada para URL do servidor (com valor padrão)
  • Campo para login
  • Campo para API Key (mascarado com asteriscos)
  • Botão para conectar

Lógica Principal de Execução

Chegou o momento de implementarmos a parte que irá fazer a mágica acontecer: colocaremos, dentro de passos lógicos, a chamada para autenticação na API e renderização nos gráficos. Abaixo, temos o código e explicação do que está sendo feito na chamada do código:

if btn_conectar:
    if not login_input or not key_input:
        st.sidebar.error("Por favor, preencha as credenciais.")
    else:
        with st.spinner("Conectando ao Maestro..."):
            token, org = login(server_input, login_input, key_input)
            if token and org:
                main(token, org)
            else:
                st.error("Falha ao obter Token. Verifique suas credenciais e a URL do servidor.")
else:
    st.info("Insira suas credenciais na barra lateral e clique em 'Conectar' para carregar os gráficos.")

Fluxo:

  1. Usuário clica no botão "Conectar"
  2. Verifica se login e chave foram preenchidos
  3. Se sim, exibe spinner e tenta fazer login
  4. Se bem-sucedido, chama main() para carregar todos os gráficos
  5. Se falhar, exibe mensagem de erro
  6. Se usuário não clicou no botão, exibe mensagem informativa

Código-fonte completo:

Veja o código-fonte completo:

import streamlit as st
import requests
import plotly.graph_objects as go
from collections import Counter

# Configurações da página Streamlit
st.set_page_config(page_title="Maestro Dashboard", page_icon="📊", layout="wide")

def login(server, login, key):
    """Realiza o login no Maestro e retorna o token e organização."""
    login_url = f"{server}/api/v2/workspace/login"
    body = {"login": login, "key": key}
    try:
        response = requests.post(url=login_url, json=body, timeout=10)
        response.raise_for_status()
        content = response.json()
        return content['accessToken'], content['organizationLabel']
    except Exception as e:
        st.error(f"Erro na autenticação: {e}")
        return None, None

def fetch_data(server, headers, endpoint):
    """Busca dados de um endpoint específico do Maestro."""

    url = f"{server}/api/v2/{endpoint}"
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
        return response.json().get('content', [])
    except Exception as e:
        st.warning(f"Erro ao buscar {endpoint}: {e}")
        return []


def main(token, org):

    headers = {'token': token, 'organization': org}

    # Buscando dados
    alerts_content = fetch_data(server_input, headers, "alerts")
    errors_content = fetch_data(server_input, headers, "error")

    # Contagem de registros de erros e alertas
    cont_erros, cont_alertas = st.columns(2)
    cont_erros.metric("Total de Alertas", len(alerts_content))
    cont_alertas.metric("Total de Erros", len(errors_content))

    # --- Processamento de Alertas ---
    alert_counts = Counter(item['botId'] for item in alerts_content)
    bots_alerts = list(alert_counts.keys())
    alert_values = list(alert_counts.values())

    # --- Processamento de Erros ---
    error_counts = Counter(item['botId'] for item in errors_content)
    bots_errors = list(error_counts.keys())
    error_values = list(error_counts.values())

    error_categories = Counter(item.get('type', 'N/A') for item in errors_content)

    # --- Layout de Gráficos ---
    st.divider() 

    conteiner_alertas, conteiners_erros = st.columns(2)

    with conteiner_alertas:
        st.subheader("⚠️ Alertas por Automação")
        fig_alerts = go.Figure(data=[go.Bar(x=bots_alerts, y=alert_values, marker_color='mediumturquoise')])
        fig_alerts.update_layout(template='plotly_dark', margin=dict(l=20, r=20, t=40, b=20))
        st.plotly_chart(fig_alerts, use_container_width=True)

    with conteiners_erros:
        st.subheader("❌ Erros por Automação")
        fig_errors = go.Figure(data=[go.Bar(x=bots_errors, y=error_values, marker_color='#EF553B')])
        fig_errors.update_layout(template='plotly_white', margin=dict(l=20, r=20, t=40, b=20))
        st.plotly_chart(fig_errors, use_container_width=True)

    st.divider()

    conteiner_categorias, conteiners_lista = st.columns([1, 2])

    with conteiner_categorias:
        st.subheader("📉 Categorias de Erros")
        if error_categories:
            pie_chart = go.Figure(data=[go.Pie(
                labels=list(error_categories.keys()), 
                values=list(error_categories.values()),
                hole=.3
            )])
            pie_chart.update_traces(hoverinfo='label+percent', textinfo='value', marker=dict(line=dict(color='#000000', width=1)))
            st.plotly_chart(pie_chart, use_container_width=True)
        else:
            st.info("Sem dados de categorias para exibir.")

    with conteiners_lista:
        st.subheader("📋 Últimos Erros Detectados")
        if errors_content:
            # Mostra os últimos 5 erros em formato de tabela
            st.table([{"Bot ID": e['botId'], "Tipo": e['type'], "Data": e.get('dateCreation', 'N/A')} for e in errors_content[:5]])
        else:
            st.write("Nenhum erro recente.")

# --- Interface - Sidebar ---
st.sidebar.title("🔑 Configurações")
server_input = st.sidebar.text_input("Server URL", value="https://developers.botcity.dev")
login_input = st.sidebar.text_input("Login")
key_input = st.sidebar.text_input("API Key", type="password")
btn_conectar = st.sidebar.button("Conectar e Atualizar")

st.title("📊 Monitoramento de Automações")
st.markdown("Visualização de Alertas e Erros capturados via API do Maestro.")

if btn_conectar:
    if not login_input or not key_input:
        st.sidebar.error("Por favor, preencha as credenciais.")
    else:
        with st.spinner("Conectando ao Maestro..."):
            token, org = login(server_input, login_input, key_input)
            if token and org:
                main(token,org)
            else:
                st.error("Falha ao obter Token. Verifique suas credenciais e a URL do servidor.")
else:
    st.info("Insira suas credenciais na barra lateral e clique em 'Conectar' para carregar os gráficos.")

Executando o projeto para renderizar os gráficos:

Com o código implementado, podemos executar o projeto para gerar uma página web no servidor local, realizar a autenticação na API e exibir os gráficos. Logo, podemos executar o seguinte código no terminal:

streamlit run main.py

Ao chamar o comando acima, mensagens no seu terminal deve aparecer, indicando o endereço que essa página web está localizada, como mostrado no print abaixo:

print-terminal

Faça um CTRL + Click com o cursor do mouse no link de Local URL, e veja a página web renderizada no seu navegador, como indicado na imagem abaixo:

print-web

Por fim, preencha os dados de Login e Key, clique no botão Conectar e Atualizar, e veja os gráficos renderizados!

Documentações e materiais de apoio:

Saiba mais

A BotCity oferece diversos endpoints de API para controlar e monitorar suas automações.